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
Hello ZX
まずは JavaScript で書いて実行してみる.
top-level-await のために,.mjs
拡張子がいいとのこと.
zx において,シェルコマンドの実行は$
によってでき,JS 関数は普段どおりに呼び出すことが出来る.
$
は非同期で呼び出されるようで,必要に応じて(ほとんどの場合必要だと思うが)await
をつける.
#!/usr/bin/env zx
await $`echo Hello zx`
console.log('Hello zx')
実行結果は以下となる.
$ echo Hello zx
Hello zx
Hello zx
Hello ZX TypeScript
冒頭に述べたとおり,TypeScript が好きなので,TypeScript で実行できるようにする.
私の環境では,そのために@types/node
をインストールする必要があった.
await
を使いたかったので,async
の IIFE で囲んだ(type: module
でいけるのかなぁ?).
先程のサンプルを書き換えると以下のようになる.
#!/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
disable verbose
シェルコマンドを実行する時に,欲しくない出力が出ちゃうよね.以下のように無効化出来る.
スクリプト内に以下を記載する.
$.verbose = false;
receive command output
シェルスクリプトを扱う上で,コマンドの結果を利用したいことがあると思う.そんな時,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
については,$
で実行しても意味がない(サブプロセスでcd
されるだけ ww)ので,専用の関数として実装されている.
cd('..');
とすると,通常のcd
コマンドのようにディレクトリを移動することが出来る.
お試し
カレントにあるファイルをfiles: string[]
に格納する方法を試してみる.
ls
ls
コマンドで受け取ってみる.
#!/usr/bin/env zx
import 'zx/globals';
$.verbose = false;
(async () => {
const files = (await $`ls`).stdout.split('\n').slice(0, -1);
console.log(files);
})();
このように,JS で普段使っているメソッドをシェルを織り交ぜながら使えるのはかなり便利なのかなと思った.
readdirSync
Nodejs のfs
を使う場合,以下のように書ける.
#!/usr/bin/env zx
import 'zx/globals';
$.verbose = false;
(async () => {
const files = fs.readdirSync('.');
console.log(files);
})();
こちらのほうが形式を整える必要がなく,必要な型で受け取れるため,便利に感じる.
おわりに
TypeScript は好きだけど,トランスパイルが入るぶん zx とは相性悪い印象・・・
また,fs
などでできる処理については,シェルコマンドよりそちらを使ったほうが扱いやすい形式で結果を受け取ることが出来るぶん,便利だと思った.
おそらく,それ自体も zx の存在価値なのだろうと思う.