2022/05/06

テクノロジー

【zx】手軽にJavaScriptにLinuxコマンドを埋め込み実行する

この記事の目次

    はじめに

    JavaScriptは闇の深い言語だのなんだの言われがちです。
    ですが、そういったツッコみを入れられやすい仕様の代わりに、敷居が低く手軽に書ける言語でもあります。

    そんなJavaScriptを愛する皆さんなら、こんなことを一度は思ったことがあるのではないでしょうか。

    「Linux環境のシェルスクリプトとか、Windowsのバッチとか、全部JavaScriptで手軽に書けたら楽だな……」

    長年バッチファイルを書いてきた人や、シェル芸を得意とする人でもない限り、これらの処理を書くのはなかなかに苦痛でしょう。JavaScriptの様な、よく見る手慣れた言語で書きたいです。

    そんな夢を叶えるツール、それがGoogle(の開発者)謹製のライブラリ、zxです。

    インストール

    実行環境として、以下の環境が必要です。

    node.js 16.0.0以上

    npmでzxをインストールします。

    npm install -g zx

    今回、-gオプションでグローバルインストールを行っていますが、しなくてもよいです。
    グローバルインストールしない場合は、npx経由でzxを叩いてください。

    ありそうな処理を書いてみる

    試しに、touchコマンドでファイルを作成し、lsコマンドを実行する処理を書いてみます。

    .mjs拡張子のファイルを作成します。
    今回は、適当にtouch2ls.mjsという名前で作成しました。

    #!/usr/bin/env zx
    
    await $`touch example_file`
    await $`ls`

    書き方としては、先頭にシェルスクリプトのようにシバンとして#!/usr/bin/env zxを書き、$`command`でLinuxコマンドを記載できます。

    注意点として、コマンドの返り値の型はProcessPromise<ProcessOutput>、Promiseが含まれていることからわかるように非同期処理です。

    touch2ls.mjsを以下のコマンドで実行します。

    C:\Users\hoge_user\temporary>zx touch2ls.mjs
    $ touch example_file
    $ ls
    example_file
    touch2ls.mjs

    ちなみに、上記のコマンドの実行環境はコマンドプロンプトです。
    コマンドプロンプトですが、存在しないlsコマンドを問題なく実行できています。

    より実践的な処理を書く

    今度はより実践的な処理として、約1GBのndjsonファイルから特定の文字列を持つ行を抽出し、それが全体の何%かを求めたいと思います。

    シェルスクリプトに詳しい方ならそれのみで書けるかと思いますが、詳しくない人間にとってはこれをシェルスクリプトで書くのは難しいはずです。

    やり方としては

    1. catコマンドで出力する結果をパイプしてgrep -cコマンドを用いて該当の行数を取得
    2. wc -lコマンドを用いて全体の行数を取得
    3. JavaScriptで割り算し、結果を出力

    という手順を取ります。

    その手順で作成したファイルがこちらです。

    #!/usr/bin/env zx
    
    const path = argv.path
    
    const grepList = (await $`cat ${path} | grep -c マイナビ`).stdout
    const fileWc = (await $`cat ${path} | wc -l`).stdout
    console.log(`result: ${grepList / fileWc * 100}%`);

    今回は、抽出対象の文字列をマイナビとしました。grepコマンドで使用しているので、-Eオプションを付ければ正規表現でも問題ありません。

    zxではコマンドラインパーサであるminimistを、グローバルな変数argvとして明示的にインストールせず使用できるので、検索対象のファイルパスは--pathオプションで取得する想定で記述します。

    コマンドの出力結果は、stdoutとして標準出力を取得できるので、そちらから取得できます。

    それでは、実際に実行してみましょう。
    今回使用するサンプルファイルjob.ndjsonのファイルサイズは1742299683、およそ1.7GBです。

    C:\Users\hoge_user\temporary>zx calc.mjs --path job.ndjson
    $ cat job.ndjson | grep -c マイナビ
    21375
    $ cat job.ndjson | wc -l
    264680
    result: 8.075789632764092%

    このように、簡単に結果を求めることができました。
    ちなみに、こちらの結果を同PCのWSL2上でtimeコマンド付きで10回実行したところ、平均約6.8秒程度で完了しています。通常のJavaScriptの処理と比較して、非常に簡潔なコードかつ高速です。

    おわりに

    今回はシェルスクリプトやバッチファイルの代替として紹介しましたが、個人的にzxはそれ以上の可能性を秘めていると感じています。

    例えば、JavaScriptの大きな弱点として大規模なデータを扱うことが難しいという点がありますが、zx経由でこれらのデータを触ることで、ある程度その問題を回避できます。
    また、本文中では紹介しませんでしたが、fetchsleepなどの通常のnode.js環境にはライブラリなしには存在しない便利な機能が、zxでは標準で存在します。

    咄嗟にちょっとした処理を書くのに何かと便利なzx、ぜひ試してみてください。

    ※本記事は2022年05月時点の情報です。

    著者:マイナビエンジニアブログ編集部