この記事で紹介しているDeno.spawn()は、その後のリリースで削除されました。
現在のDenoにおけるサブプロセス作成方法は以下の記事をご参照ください。
Deno.run
は非推奨になるので代替手段(Deno.Command
、dax)- Deno.run と Deno.spawn と Deno.Command のどれを使えば良いのか
- Deno 1.31で安定化されたプロセス起動 API Deno.Command を使ってみる
この記事では、2022年内の8ヶ月間のみに存在したDeno.spawn()
APIについて解説しています。記録のために内容は残しておきますが、現在では役に立ちません。
記事本文はここをクリック
Deno1.21以降、サブプロセスの生成が簡単になります。
const { code, stdout, stderr } = await Deno.spawn("echo", { args: ["hello"] });
console.log(new TextDecoder().decode(stdout)); //=> stdoutの内容
console.log(new TextDecoder().decode(stderr)); //=> stderrの内容
console.log(code); //=> 0
従来のAPI
従来は、サブプロセスの生成にはDeno.run()
という関数を用いていました。
const p = Deno.run({
cmd: ["echo", "hello"],
stdout: "piped",
stderr: "piped",
});
const status = await p.status();
const stdout = await p.output();
const stderr = await p.stderrOutput();
この関数は低レベルなAPIなので、上記のような「コマンドを実行して結果を受け取る」というプログラムでも長めのコードを書く必要がありました。
今回導入されたAPI
Deno1.21では、
-
Deno.spawn
- コマンドを実行して結果を受け取る高レベルAPI(非同期) -
Deno.spawnSync
-Deno.spawn
の同期版 -
Deno.spawnChild
- ストリーミング処理ができる低レベルAPI
という3つの新しいサブプロセスAPIが入りました。
それぞれ順番に解説していきます。
※これらのAPIはまだunstable扱いなので、実行の際は--unstable
フラグを付ける必要があります。
Deno.spawn()
Deno.spawn
はコマンドを実行して結果を受け取る関数です。
https://doc.deno.land/deno/unstable/~/Deno.spawn
const { success, code, signal, stdout, stderr } = await Deno.spawn("echo", { args: ["hello"] });
console.log(new TextDecoder().decode(stdout)); //=> stdoutの内容
console.log(new TextDecoder().decode(stderr)); //=> stderrの内容
console.log(success); //=> true
console.log(code); //=> 0
console.log(signal); //=> null
この関数は非同期なので、結果を得るためにawait
してやる必要があります。
サブプロセスが終了すると、stdout
(標準出力)、stderr
(標準エラー出力)、終了ステータスの情報が返されます。
stdout
とstderr
はUint8Arrayというバイト配列で返されます。これを文字列に変換するには、new TextDecoder().decode()
を使います。
Deno.spawnSync()
Deno.spawnSync()
はDeno.spawn()
の同期版です。
https://doc.deno.land/deno/unstable/~/Deno.spawnSync
const { success, code, signal, stdout, stderr } = Deno.spawnSync("echo", { args: ["hello"] });
console.log(new TextDecoder().decode(stdout)); //=> stdoutの内容
console.log(new TextDecoder().decode(stderr)); //=> stderrの内容
console.log(success); //=> true
console.log(code); //=> 0
console.log(signal); //=> null
コマンド処理させるだけの簡単なスクリプトは同期APIであるDeno.spawnSync()
を、
サーバーやUIを表示するプログラムの中で使う場合はスレッドをブロックしない非同期APIであるDeno.spawn()
を使うとよいでしょう。
Deno.spawnChild
Deno.spawnChild
はDeno.spawn
より高度な処理が行えるAPIです。
https://doc.deno.land/deno/unstable/~/Deno.spawnChild
- 標準入出力のストリーミング処理(パイプ)
- コマンド実行中のkill
が可能です。
Deno.spawnChild
はWebStreamに対応しています。child.stdout
やchild.stderr
がReadaleStream
になっており、ファイルなどに出力をパイプすることができます。
const child = Deno.spawnChild("echo", { args: ["hello"] });
const file = await Deno.open("./output.txt", { write: true });
child.stdout.pipeTo(file.writable); // stdoutをfileへパイプして書き込み
// あるいは
// child.stdout.pipeTo(Deno.stdout.writable); // 自分のstdoutへパイプする
// `await child.status` で、プロセス終了まで待つ
console.log(await child.status); //=> { success: true, code: 0, signal: null }
// ストリーミングが不要な場合は、.output()で最終的な結果を得ることができる。
// const { stdout, stderr, code } = await child2.output();
Deno.spawnChild
はstdinへのストリーミングにも対応しています。stdinへ書き込むときは、オプションに"piped"
を指定します。
const child1 = Deno.spawnChild("echo", {
args: ["console.log('hello')"],
stderr: "inherit", // stderrが不要な時は"inherit"か"null"にする
});
const child2 = Deno.spawnChild(Deno.execPath(), {
stdin: "piped", // stdinに書き込むときは"piped"に設定する
});
// child1の標準出力をchild2にパイプ
await child1.stdout.pipeTo(child2.stdin);
const { stdout, stderr, status } = await child2.output();
console.log(new TextDecoder().decode(stdout)); //=> 標準出力
console.log(new TextDecoder().decode(stderr)); //=> 標準エラー出力
console.log(status); //=> { success: true, code: 0, signal: null }
child1.stdout.pipeTo(child2.stdin)
と書くことで、child1の標準出力をchild2の標準入力にパイプすることができます。.pipeTo()
の代わりに.pipeThough(t: TransformStream)
を使うと、間に変換用のTransformStreamを噛ませたうえでパイプできます。
サブプロセスを途中で殺すには、.kill(シグナル名)
します。
const child = Deno.spawnChild(Deno.execPath());
child.kill("SIGKILL"); // プロセスをkill
const { success, code, signal, stdout, stderr } = await child.output();
console.log(success); //=> false
console.log(code); //=> 1
console.log(signal); //=> null
引数に渡すオプション
Deno.spawn
/Deno.spawnSync()
/Deno.spawnChild
の引数には、オプションを渡すことができます。このオプションの型定義は以下のようになっています。
// Deno.SpawnOptions型の型定義
interface SpawnOptions {
/** コマンドライン引数 */
args?: string[];
/** 親プロセスの環境変数を引き継ぐかどうか。デフォルトはfalse(引き継ぐ) */
clearEnv?: boolean;
/** 子プロセスのカレントディレクトリ(デフォルトは親プロセスと同じディレクトリ) */
cwd?: string | URL;
/** 子プロセスの中で使える環境変数 */
env?: Record<string, string>;
/** 標準出力(デフォルトは"piped") */
stdout?: "piped" | "inherit" | "null";
/** 標準エラー出力(デフォルトは"piped") */
stderr?: "piped" | "inherit" | "null";
/** 標準入力(デフォルトは"null") */
stdin?: "piped" | "inherit" | "null";
/** 子プロセスのグループid */
gid?: number;
/** 子プロセスのユーザーid */
uid?: number;
/** プロセスをkillするために使うAbortSignal */
signal?: AbortSignal;
}
まとめ
-
Deno.run()
に代わる新しいサブプロセスAPIが導入された-
Deno.spawn
- コマンドを実行して結果を受け取る高レベルAPI(非同期) -
Deno.spawnSync
-Deno.spawn
の同期版 -
Deno.spawnChild
- ストリーミング処理ができる低レベルAPI
-
-
Deno.spawnChild
はWeb Streamに対応している
特にDeno.spawnSync
は、makefileやbash scriptをDenoで置き換えることができそうで、便利だと思います。
ただし、記事執筆時点ではまだ--unstable
フラグ付きなので、APIが変更される可能性がある点に注意してください。
(実際、返り値のステータスの取り方がv1.24から変更されています。)