ys memos

Blog

zx使ってみたけどムズイ


shell

2021/10/10


zxというものがある.

これは,Google が開発・公開している OSS であり,シェルスクリプトを JS で記述することができるというツールである.

Bash is great, but when it comes to writing scripts, people usually choose a more convenient programming language. JavaScript is a perfect choice, but standard Node.js library requires additional hassle before using. The zx package provides useful wrappers around child_process, escapes arguments and gives sensible defaults.

とあり,シェルスクリプトを JavaScript で書くために必要な準備をしてくれるらしい.

また,私は TypeScript が好きなので,TypeScript で使う方法も模索してみた.


インストールは,以下のコマンド.

$ npm i -g zx

まずは JavaScript で書いて実行してみる.

top-level-await のために,.mjs拡張子がいいとのこと.

zx において,シェルコマンドの実行は$によってでき,JS 関数は普段どおりに呼び出すことが出来る.

$は非同期で呼び出されるようで,必要に応じて(ほとんどの場合必要だと思うが)awaitをつける.

hello_zx.mjs
#!/usr/bin/env zx

await $`echo Hello zx`

console.log('Hello zx')

実行結果は以下となる.

$ echo Hello zx
Hello zx
Hello zx

冒頭に述べたとおり,TypeScript が好きなので,TypeScript で実行できるようにする. 私の環境では,そのために@types/nodeをインストールする必要があった.

awaitを使いたかったので,asyncの IIFE で囲んだ(type: moduleでいけるのかなぁ?).

先程のサンプルを書き換えると以下のようになる.

hello_zx.ts
#!/usr/bin/env zx

import 'zx/globals';

(async () => {
  await $`echo Hello ZX TS`;
  console.log('Hello ZX TS');
})();

実行結果は先ほどと同様で,以下になる.

$ echo Hello ZX TS
Hello ZX TS
Hello ZX TS

シェルコマンドを実行する時に,欲しくない出力が出ちゃうよね.以下のように無効化出来る.

スクリプト内に以下を記載する.

$.verbose = false;

シェルスクリプトを扱う上で,コマンドの結果を利用したいことがあると思う.そんな時,bash script なら$()や backquote を使うと思う.

pwdの結果を変数に保存する場合,zx なら,以下のようにするとできる.

const result = await $'pwd';
/*
{
	stdout: xxx,
	stderr: yyy,
	exitCode: z
}
 */

コメントとして書いてあるのが,resultに格納される内容である.しかし実際,必要となるのはstdoutの値だけだったりする.

そのためには,result.stdoutのように扱ってもよいし,以下のように直接受けることも出来る(ちなみに,エラー時には zx の標準設定だと終了してくれる).

const current_path = (await $`pwd`).stdout;
console.log(current_path);
/*
xxx
*/

また,verboseを無効にした上でも,コマンドの結果を標準出力したい場合は,以下のようにする.

await $`pwd`.pipe(process.stdout);
/*
xxx
*/

cdについては,$で実行しても意味がない(サブプロセスでcdされるだけ ww)ので,専用の関数として実装されている.

cd('..');

とすると,通常のcdコマンドのようにディレクトリを移動することが出来る.


カレントにあるファイルをfiles: string[]に格納する方法を試してみる.


lsコマンドで受け取ってみる.

list_file.ts
#!/usr/bin/env zx

import 'zx/globals';
$.verbose = false;

(async () => {
  const files = (await $`ls`).stdout.split('\n').slice(0, -1);
  console.log(files);
})();

このように,JS で普段使っているメソッドをシェルを織り交ぜながら使えるのはかなり便利なのかなと思った.


Nodejs のfsを使う場合,以下のように書ける.

list_file_node.ts
#!/usr/bin/env zx

import 'zx/globals';
$.verbose = false;

(async () => {
  const files = fs.readdirSync('.');
  console.log(files);
})();

こちらのほうが形式を整える必要がなく,必要な型で受け取れるため,便利に感じる.


TypeScript は好きだけど,トランスパイルが入るぶん zx とは相性悪い印象・・・

また,fsなどでできる処理については,シェルコマンドよりそちらを使ったほうが扱いやすい形式で結果を受け取ることが出来るぶん,便利だと思った.

おそらく,それ自体も zx の存在価値なのだろうと思う.


関連タグを探す