Google から公開されている Bash の JavaScript ラッパー”zx”を触ってみた

Bash,JavaScript

はじめに

Twitter で少し前に盛り上がっていた、 google/zx という NPM ライブラリに触れてました。

新しいツールを触る機会は家の手伝いや子守をしていてここ数年全く取れていなかったのですが、今日はたまたま時間が作れました。

検証環境

$ uname -moi
x86_64 MacBookPro16,1 Darwin

$ bash -version | head -n 1
GNU bash, version 5.1.8(1)-release (x86_64-apple-darwin20.3.0)

$ node --version
v15.12.0

$ yarn --version
1.22.10

ツールの紹介

Bash は偉大だけと、多くのケースでスクリプトを書こうとするともっと高級な言語が選択されるでしょう。(もっと文法や型に制約が強いほうがチーム開発には向いていますよね。)

僕もちょっとした処理を Bash で書こうものなら、他のエンジニアの当たりがあります(^_^;)

JavaScript も良い選択肢ですが、JavaScript から Bash コマンドを呼び出す手間が面倒です。

そんな手間を違和感なく解決してくれるのが zx とのことです。

インストール

npm と yarn のそれぞれの方法を紹介していますが、どちらにしてもグローバルインストールすることが推奨されているようです。

npm の場合

npm i -g zx

yarn の場合

僕はこちらでインストールしました。

yarn global add zx

使い方

ファイルの拡張子

.mjs という拡張子のファイルを作成し、そこにスクリプトを記述してい来ます。

拡張を元に await がトップレベルで利用できるようになります。( async な無名関数でくるんだりが不要 )

もちろん .js という通常の JavaScript ファイルの拡張子でも zx を使うことはできますが、この場合は void async function () { ... }() でくるんでやる必要があります。

ファイルの SheBang(シバン)

ファイルの一行目について。

Bash だと

#!/usr/bin/env bash

などと記述するあれですが、 .mjs ファイルでは

#!/usr/bin/env zx

と書きておきます。

サンプルプログラム

カレントディレクトリのファイル数をカウントし、標準出力するスクリプトがサンプルプログラムとして掲載されていました。

早速作成してみます。

$ touch ./script.mjs

$ chmod +x ./script.mjs

$ vi ./script.mjs

コードを記述していきます。 ( $`...` の部分に好きなシェルコマンドを記述できるようです。実行結果が文字列で返ってくると。これは面白い。 )

#!/usr/bin/env zx

let count = parseInt(await $`ls -1 | wc -l`);
console.log(`Files count: ${count}`);

作成したら実行してみます。

$ ls -1 | wc -l
2
Files count: 2

あ、そういう感じなんですね。
$`...` で実行した結果をスクリプト内で文字列として取得できる以外に、標準出力にも流れちゃうんですね。

実行コマンドは標準出力に出力させない

$`...`で実行したコマンドは標準出力に流さないようにするには、$.verbose という変数の値を変更します。

#!/usr/bin/env zx

$.verbose = false;

let count = parseInt(await $`ls -1 | wc -l`);
console.log(`Files count: ${count}`);

実行してみます。

Files count: 2

$`...` のコマンドとその実行結果が出力されなくなりました。

提供されている関数、変数

cd() 関数

カレントディレクトリを変更する関数です。

$`...` の中で移動してもいいと思いますが、次の $`...` では引き継がれません。

#!/usr/bin/env zx

await $`pwd; ls -1 | head -n 5`;

console.log("---");
// 移動してみる
await $`cd /etc; pwd; ls -1 | head -n 5`;

console.log("---");
// せっかく移動してもカレントディレクトリは元のディレクトリに戻っている
await $`pwd; ls -1 | head -n 5`;

console.log("---");
// cd() 関数を実行
cd("/etc");

console.log("---");

await $`pwd; ls -1 | head -n 5`;

console.log("---");

await $`pwd; ls -1 | head -n 5`;

実行します。

$ ./script.mjs
$ pwd; ls -1 | head -n 5
/tmp/work
script.mjs
---
$ cd /etc; pwd; ls -1 | head -n 5
/etc
afpovertcp.cfg
aliases
aliases.db
apache2
asl
---
$ pwd; ls -1 | head -n 5
/tmp/work
script.mjs
---
$ cd /etc
---
$ pwd; ls -1 | head -n 5
/private/etc
afpovertcp.cfg
aliases
aliases.db
apache2
asl
---
$ pwd; ls -1 | head -n 5
/private/etc
afpovertcp.cfg
aliases
aliases.db
apache2

fetch() 関数

node-fetch で提供される fetch() 関数が使えます。

#!/usr/bin/env zx

let resp = await fetch("http://wttr.in");
if (resp.ok) {
  console.log(await resp.text());
}

実行します。

$ ./script.mjs

いきなりカラフルな天気予報が出てきてびっくりしました。大阪。

sleep() 関数

setTimeout 関数のラッパー。

シンプルなインタラクティブコマンドラインインターフェースを提供できます。

#!/usr/bin/env zx

await $`date`;

await sleep(5000);

await $`date`;

実行。

$ ./script.mjs
$ date
金  5 14 22:18:38 JST 2021
$ date
金  5 14 22:18:43 JST 2021

全部 await をつけないといけないのは少し面倒くさい。

chalk パッケージ

chalk パッケージを明示的に import しなくても利用できます。

#!/usr/bin/env zx

console.log(chalk.blue("Hello world!"));
console.log(chalk.yellow.underline("Hello world!"));
console.log(chalk.green.italic("Hello world!"));
console.log(chalk.red.bold("Hello world!"));

実行。

$ ./script.mjs

カラフル。見やすい。

fs パッケージ

fs パッケージを import なしで使える、ということですがあんまり旨味を感じません。

let content = await $`cat FILE.txt`;

で十分満たせているためです。

os パッケージ

こちらもなくていいかな。OS 情報、シェルコマンドで取得できるので。

$.shell

変数に代入して、デフォルトシェルを変更できます。

$.shell = "/usr/local/bin/zsh";

$.prefix

Bash シェルスクリプトの最初のおまじない、 set を設定するための変数です。

デフォルト値はすでに以下のようになっているとのことです。

$.prefix = "set -euo pipefail;";

ですので

  • エラー発生時に中断する
  • 定義されていない変数を参照したらエラー
  • パイプラインのいずれかでエラーが発生したらエラー扱い

$`...` 実行時のデフォルトの挙動となっているようです。

これは納得。

$.verbose

$`...` 実行時のコマンドと実行結果が標準出力されるかどうかの設定。

デフォルトは true とのことです。

個人的にはデフォルトは false にしてほしかった。

リモートスクリプトの実行

Web 上に転がっているスクリプトを実行することもできるそうです。

$ zx https://medv.io/example-script.mjs

ただし、URL は https である必要があります。

ひとこと

便利そうではありますが、使わない気もします。

ただ、Bash シェルスクリプトだと計算処理が貧弱なんですよね。

それから JSON データを操作したりする場合も便利そうな気はしますが、 jq 使えたらいいかなと思わないことも無いです。

chalk で色んな色、太字、斜体、アンダーラインなどの文字が出力できるのはコマンドラインインターフェイス作るのに良さそうな気もしますが、各実行環境にインストールしてもらう必要がある点だけ、配布時に煩わしさを伴うかも。

Bash,JavaScript