Node.js でコマンドの実行結果を1行ずつ取得する方法

Published: 2022/10/9


Node.js でサーバー側の軽いツールを作ろうとすると、コマンドを実行して、その結果を1行ずつ、それもストリーム処理で、行が取得できた端からその行を await したくなる。 具体的には、コマンドの実行結果の標準出力を、 Async Generator として for await of して行を取得したい。 どうやるかが自明ではなかったので、それについて調べたメモ。

答え: readline と child_process.spawn を使う

まず、 Node.js にてコマンドを実行するためには、子プロセス作成系の組込みモジュールである child_process を使う。 その中で、 exec の関数は、実行結果の最終的な文字列を全部まとめて取得するしかできないので、 spawn の方を利用するべき、となる。

spawn 関数の結果は、コールバックにて Buffer が chunk として渡される結果を利用することになり、これは少しやり辛い。 このようなストリーム API に対して、良い感じに行毎に読み込むライブラリは、やはり組み込みである readline が利用される模様。 (この公式資料のこの部分にもある通り、ストリームから行毎の async iterator を取り出すのには汎用的にこのライブラリを使うらしい)

これらのライブラリを組合せて、以下のようにするとコマンドの実行結果を1行毎に for await of することができる。

const childProcess = require('node:child_process');
const util = require('node:util');
const readline = require('node:readline');

const lsCommand = childProcess.spawn('ls', ['-al']);
const rl = readline.createInterface({
  input: lsCommand.stdout,
  crlfDelay: Infinity
});

const main = async () => {
  for await (const line of rl) {
    console.log(line)
  }
}

main()

Tags: nodejsjavascript

関連記事