ちょっと複雑なシェルスクリプトをJavaScriptで書く
/lacolaco/lacolaco.iconはzx歴 3-4ヶ月ってところ (2021-08)
嬉しいところ
async/awaitが使える
配列が扱いやすい
モジュールで再利用しやすい
他のNode.jsライブラリと併用できる
Prettierでフォーマットしやすい
Lintしやすい
エディタ支援が安心
Made by Google
微妙なところ
JavaScriptプログラマ以外にとっては無用
とはいえシェルスクリプトによほど慣れてる人以外はよく整理されたJavaScriptのほうがセマンティクスを読み取りやすいのではないか
使い方
スクリプト自体はこんな感じ(公式READMEより)
code:js
await $cat package.json | grep name
let branch = await $git branch --show-current
await $dep deploy --branch=${branch}
await Promise.all([
$sleep 1; echo 1,
$sleep 2; echo 2,
$sleep 3; echo 3,
])
let name = 'foo bar'
await $mkdir /tmp/${name}
(なんでletかは知らない。constは普通に使える)
zxでもいくつかの派閥(?)がある。
直接実行する派: $ ./script.mjs
shebang zx派
#!/usr/bin/env zx
事前に npm install -g zx が必要で、環境側の準備が必要
shebang node派
#!/usr/bin/node
zx は node_modules から引っ張ってくるので比較的環境づくりが楽
nodeコマンド派: $ node ./script.mjs
commonjs派
Node.js v14まではこっちじゃないと苦労する
ESModule派
Node.js v16以降ならESM使いやすいのでこっちでもまあまあ
/lacolaco/lacolaco.iconは 直接実行 + shebang node派
他のNode.jsライブラリと併用するとか考えたときに devDependencies に一緒にzxがいるほうが収まりがいい
zx のAPIはTypeScriptの型定義があるからエディタ支援もあるんだけどshebang zx派だとグローバルに $ が生えるので型定義を適用するのがひと手間
ESLintでグローバル変数許可する手間もあるし
import { $ } from 'zx' ならnode_modules内に型定義が同梱されてれば適当に反映されてくれる
.mjsで書く
ESMかTypeScriptなら Top-Level Awaitが使える
awaitするために async main() で囲む必要がない
Moduleには __dirnameがない問題
import.metaで解決する
const dirname = path.dirname(new URL(import.meta.url).pathname);
使いどころ
もうbashのこと覚えたり思い出したりしたくないのでシェルスクリプト必要かなって思ったら即zxしてる
使い分けを考える時間が無駄っぽい
だいたいコードベースがJS/TSのプロジェクトでしか作業してないのもあるが
基本的にはnpm scripts経由で実行させてる
code:json:こんな感じ
"scripts": {
"foobar": "./tools/foobar.mjs"
}
ワンライナーで済むスクリプトはnpm-scriptsに直接書いてる
ワンライナーでは無理かなってなったらzxに移る
ただ並列でいくつか実行するためだけにnpm-run-all に依存したくないなーとか
片手間レベルで書くDockerとの相性がいい気がしている
Dockerfileの RUN でいろいろ実行するために書くbashがつらい
Docker内でzx使えるようにしとけば RUN ./script.mjs できる