zxで気になった部分を例とともに紹介

zxのドキュメントを読んで特に気になったった部分を例とともに紹介します。
2022.08.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも。CX事業本部Delivery部のえーたん(@eetann092)です。

zxが面白そうだったので素振りしました。 zxのドキュメントを読んで特に気になったった部分を例とともに紹介します。

zxとは?

zxを使うと、JavaScriptのファイルとしてシェルスクリプトを書くことができます。

以下はzxを使わずにchild_processを使って書いた例です。

import { exec } from "child_process";

exec('ls', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log(`${stdout}`);
  console.error(`${stderr}`);
});

実行結果の例は以下です。

README.md
node_modules
package-lock.json
package.json
src

参考:Child process | Node.js v18.7.0 Documentation

次に、zxを使った例です。 $とバッククオートを使ってコマンドを書きます。

import 'zx/globals'

await $`ls`

zxを使えば、標準出力ためにコールバックやconsole.logを書く必要がなく、コードが短くなります。

$ ls
README.md
node_modules
package-lock.json
package.json
src

実行コマンドがわかる

実行したコマンドが何かを出力してくれます。

以下、コマンド実行例のためのスクリプトです。

import 'zx/globals'

await $`git checkout $(echo "$(echo "$(git --no-pager branch -vv)" | fzf +m)" | awk '{print $1}' | sed "s/.* //")`;

以下が出力です。

$ git checkout ……というように、どんなコマンドを実行したのかが表示されます。 また、色つきであるため、自分でいちいちconsole.log("$ git checkout ……")のようにコードを書くよりも分かりやすいです。

どんなコマンドが実行されたかを表示させたくない場合、以下のように$をいじります。

$.verbose = false

出力を非表示にしたい

echo ${content} | pbcopyのように、変数展開をしたいときもあると思います。 変数の中の文字列が長くて出力をしたくない場合は、以下のように最後にquiet()をつけます。

await $`echo ${content} | pbcopy`.quiet()

TypeScriptで実行したい

TypeScriptで書かれたファイルをzxで実行したい場合、node --loader ts-node/esm hoge.tsのようにts-node経由で実行します。

import matter from "gray-matter";
import "zx/globals";

function devideMetaInfo(file: string): matter.GrayMatterFile<string> {
  return matter.read(file);
}

async function sendToClipboard(content: string) {
  // Mac
  await $`echo ${content} | pbcopy`.quiet();
}

void (async function () {
  const metaInfo = devideMetaInfo("./sample.md");
  await sendToClipboard(metaInfo.content);
})();
node --loader ts-node/esm src/zx-md-copy.ts

参考:Release 5.0.0 · google/zx

パイプラインの区切りを明確にしたい

以下のように.pipeを生やせばパイプラインを使えます。

import "zx/globals";

const branches = await $`git --no-pager branch -vv`;
const branch = await $`echo "${branches}"`.pipe($`fzf +m`);
const branchName = await $`echo "${branch}"`
  .pipe($`awk '{print $1}'`)
  .pipe($`sed "s/.* //"`);
await $`git checkout ${branchName}`;

短いコマンドであれば1行で書いたほうが良いかもしれませんが、複数のパイプラインを使う場合は.pipeを使えば区切りが明確になります。

参考:zx/process-promise.md at main · google/zx

オプションがたくさんあるので配列に入れたい

zxでは、配列は勝手にスペースで連携して展開してくれます。オプションがたくさんある時に便利そうです。

import "zx/globals";

const options = [
  "s3://mybucket",
  "--recursive",
  "--human-readable",
  "--summarize",
];

await $`aws s3 ls ${options}`;

参考:Array of arguments

色をつけたい

zxでは、chalkを内蔵しています。chalkを使うことで、簡単に出力に色を付けることができます。

import "zx/globals";

let name = await question('名前を入力してね:')
echo`Hello, ${name}`
echo(chalk.greenBright(`Hey, ${name}!`))
echo(chalk.bgGreenBright.black(`Oh, ${name}!`))
echo(chalk.underline(`ここはテストに出ますよ、${name}`))

chalk.[....](string, [string...])の形式で、簡単にスタイルを設定できます。

また、上記の例の通りzxではquestionを使ってユーザーの入力を取得したり、echo()console.logのように出力が可能です。

スクリプトの引数を使いたい

zxではminimistを内蔵しています。 argv変数から引数にアクセスできます。

import "zx/globals";

if (argv.h) {
  echo`hoge hoge`
}
if (argv.f) {
  echo`foo`
}
if (argv.b) {
  echo`bar`
}

let name = argv.name;
echo`Hello, ${name}`
echo(chalk.underline.bgYellow.black("引数全部表示する"))
echo(argv._)
npx zx src/zx-argv.mjs -hb --name="Kerry" aaaaa bbbb ccccc

実行中であることをspinnerを使って表示

zxにはspinner()があります。ぐるぐるします。

import "zx/globals";
import { spinner } from 'zx/experimental'

await spinner('working...', () => $`sleep 3`)
echo(chalk.bgGreen.black("Finish!"))

Markdownのコードブロックに書かれたスクリプトを実行

zxにはMarkdownのコードブロックに書かれたスクリプトを実行する機能もあります。 コードよりもドキュメント量の方が多くなる時に便利そうです。

まず、以下がサンプルのMarkdownファイルです。

script.md

このMarkdownファイルのコードブロックの内容が実行できる。

```js
// コードブロックの言語指定はjsにした
echo`directory: ${__dirname}`
echo`finename: ${__filename}`
```

# 言語指定
```
console.log('言語指定をしていないコードブロック')
console.log('実行されないよ!')
```

```bash
# コードブロックの言語指定はbashにした
echo "bashbashbash"
```

# 別のコードブロックの変数
別のコードブロックの変数も使える。

```js
const bar = 'Kerry'
const foo = 'HeyHey'
```

```js
echo`${foo}, ${bar}`
```

↓実行するためのコマンドです。

npx zx src/script.md

実行結果は以下です。

directory: /Users/kerry/ghq/github.com/eetann/suburi-zx/src
finename: /Users/kerry/ghq/github.com/eetann/suburi-zx/src/script.md
$
> # コードブロックの言語指定はbashにした
> echo "bashbashbash"
>
bashbashbash
HeyHey, Kerry

コードブロックには言語指定としてjsbashshのいずれかが必要です。また、言語指定にbashshを書いた場合は、表示が異なるようです。以下に該当する部分を再度表示します。

$
> # コードブロックの言語指定はbashにした
> echo "bashbashbash"
>

先頭の文字が変わったり、コメントアウトも表示するようです。

${__dirname}${__filename}のような変数も使えます。

リンク集

今回使用したサンプルのコードは以下のリポジトリにあります。

参考