zx のすゝめ
この記事はときは会アドベントカレンダー 2023の 3 日目の記事です。
シェルスクリプトはすきだけど。
例えば bash を使ってシェルスクリプトを書きたい場面って結構あります。開発中の作業はシェルから行うことが多いため、開発をしていてちょっとした自動化をするとなるとシェルスクリプトを使うのが一番簡単だったりします。自分もシェルスクリプトはすきですが、以下のような不便を感じることもあります。
- 扱えるデータが基本的に全て文字列なので、マップやリストも文字列として上手く処理する必要がある
- 例えば拡張子を取得する、スクリプトファイルと同じディレクトリを取得するというよくある処理をするにも複雑な記述になる
- 処理の分岐や結合を行おうとすると複雑なパイプや xargs を駆使する必要がある
- 文字列以外のデータ型は bash や zsh などの独自の拡張に頼る必要があり、可搬性が低い
- 少しだけ複雑な文字列操作をしようとすると grep や awk、sed など別のアプリケーションに依存する必要があり、可搬性が低い
例えば、bash スクリプトを使って、スクリプトファイルが置いてあるディレクトリの内容を一覧しようとすると、以下のようになります。
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
ls $"{SCRIPT_DIR}"
この SCRIPT_DIR を設定するのもハックの積み重ねですし、この場合は bash でしか動かせません。また、 4 行目の $"{SCRIPT_DIR}"
のダブルクォートを忘れると、パス名に半角スペースが入っている場合にバグります。自分はいまだに bash スクリプトを使いこなせる気がしていません。
では、例えば Python や Node.js 等々を使えばいいじゃないかということになったとしても、シェルコマンドを実行することを主たる目的としていないため、サクサク書けるものでもなかったりします。
以上の辛さを完全に解決してくれるのが zx です。
Hello, world!
では zx を導入して Hello, world! してみましょう。 brew でも npm でもすきなものを使えば OK です。インストールせずに npx 経由で呼ぶのもおすすめです。
# brew
brew install
# npm
npm install -g zx
# インストールせずに最新を使う
npx -y zx@latest --version
# bashrc 等に以下の一行を追加
alias zx='npx -y zx@latest'
#!/usr/bin/env zx
const userName = $`whoami`
console.log(`Hello, ${userName}`)
実行
zx hello.mjs
google/zx
zx はなんと Google 謹製のようです。ドキュメントもしっかりしているので安心して使えると思います。
npm プロジェクトに組み込んで使う
特に npm プロジェクトに組み込んで、ちょっとした自動化を行えるのが、最大の利点の 1 つではないでしょうか。以下のようなプロジェクト構成で、 package.json からバージョンを取得して表示するスクリプトを考えます。
.
├── scripts
│ └── hello.mjs
└── package.json
npm プロジェクトに zx をインストールします。
# 開発依存として追加
npm install -D zx
#!/usr/bin/env zx
const rootDir = path.join(__dirname, '..')
const packageJson = JSON.parse(await $`cat ${rootDir}/package.json`)
console.log(packageJson.version)
{
"scripts": {
"hello": "zx scripts/hello.mjs"
}
}
npm run hello
version を取得できたかと思います。
quiet
さきほどのスクリプトだと、cat の内容がそのまま印字されてしまうので、使いにくい場面もあるかと思います。そういう場合は quiet で対応できます。
#!/usr/bin/env zx
const rootDir = path.join(__dirname, '..')
const packageJson = JSON.parse(await quiet($`cat ${rootDir}/package.json`))
console.log(packageJson.version)
さらに可搬性を高めたい場合
cat が使えない環境もあると思います。そういう場合は直接 Node.js の機能を使ってしまうのがよいと思います。
const rootDir = path.join(__dirname, '.')
const packageJson = JSON.parse((await fs.readFile(`${rootDir}/package.json`)).toString())
console.log(packageJson.version)
npm プロジェクトに導入している場合は特に、npm パッケージをすきなだけ使えますので、可搬性を気にせず開発できるかと思います。
まとめ
みんな zx さんをよろおねです!!!
Discussion