ちょっと話題の記事

Google発のJavaScriptで書けるシェル 「zx」

2021.05.11

Introduction

シェルを書きたいときにBashは手軽に使えるけど、
少し込み入ったスクリプトを書こうとするとちょっと面倒。
NodeいれてJavaScript書くのもいいけど、
いろいろセットアップが手間。
そんな人にピッタリはまるかもしれないのがzx
Google発、JavaScriptで記述できるシェルです。

Top Level await使ってシェルコマンドがそのまま使用可能です。
また、Promise.allでコマンド並列実行ができたりするので便利です。

Environment

  • OS : MacOS 10.15.7
  • Node : v14.16.1

Top Level awaitがサポートされたNode(v14.8)以降が必要ぽい?

Setup

では早速セットアップしてみます。
npmでzxをグローバルインストール。

% npm i -g zx

% zx
usage: zx <script>

Try

zxではスクリプトを .mjs拡張子にします。
(トップレベルawaitを使えるようにするので)
拡張子を.jsにしたい場合、void async function () {...}()とスクリプトをwrappします。

まずはscript.mjsファイルを作成し、Hello Worldしてみます。

#!/usr/bin/env zx
console.log("Hello zx!");

スクリプトの先頭にzxシェバンを記述。

実行権限を付与して、

% chmod +x ./script.mjs

zxで実行。

% zx ./script.mjs
Hello zx!

$Command でコマンド実行できます。
実際はchild_processのexec関数を使ってコマンドを実行して、
Promise を返しているとのこと。

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

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

Promise.allでコマンドの並列実行も簡単に書けます。

await Promise.all([
  $`sleep 1; echo 1`,
  $`sleep 2; echo 2`,
  $`sleep 3; echo 3`,
]);

node-fetchのwrapperでhttp通信。

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

readlineのwrapperで対話形式のシェルも楽に書ける。

let name = await question('What is your username? ')
let token = await question('Choose env variable: ', {
  choices: Object.keys(process.env)
})

console.log(`${name},${token}`);

urlを指定して、リモート先にあるスクリプトを実行することも可能。

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

Summary

Node(npm)は必要ですが、使い勝手のよさそうなシェル環境が容易に構築できるので
よさそうです。(ワンバイナリでほしいという意見もありますが)

References