6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Deno1.21~】Denoのサブプロセス生成が簡単になった(`Deno.spawn()`)

Last updated at Posted at 2022-04-23

この記事で紹介しているDeno.spawn()は、その後のリリースで削除されました

現在のDenoにおけるサブプロセス作成方法は以下の記事をご参照ください。

この記事では、2022年内の8ヶ月間のみに存在したDeno.spawn() APIについて解説しています。記録のために内容は残しておきますが、現在では役に立ちません。

記事本文はここをクリック

Deno1.21以降、サブプロセスの生成が簡単になります。

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

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(標準エラー出力)、終了ステータスの情報が返されます。
stdoutstderrUint8Arrayというバイト配列で返されます。これを文字列に変換するには、new TextDecoder().decode()を使います。

Deno.spawnSync()

Deno.spawnSync()Deno.spawn()の同期版です。

https://doc.deno.land/deno/unstable/~/Deno.spawnSync

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.spawnChildDeno.spawnより高度な処理が行えるAPIです。

https://doc.deno.land/deno/unstable/~/Deno.spawnChild

  • 標準入出力のストリーミング処理(パイプ)
  • コマンド実行中のkill

が可能です。

Deno.spawnChildはWebStreamに対応しています。child.stdoutchild.stderrReadaleStreamになっており、ファイルなどに出力をパイプすることができます。

Deno.spawnChildの使い方
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"を指定します。

stdinへ書き込む
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(シグナル名)します。

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から変更されています。)

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?