Node.jsでシェルコマンドや外部プログラムを実行したい場面は多くあります。ビルドツールの実行、システム情報の取得、他言語で書かれたスクリプトの呼び出しなど、用途は様々です。

本記事では、node:child_processモジュールが提供する4つの主要API(exec()execFile()spawn()fork())の違いと適切な使い分けを解説します。標準入出力のハンドリング、終了コードの取得、プロセス間通信(IPC)まで、実践的なコード例を通じて習得できます。

実行環境

項目 バージョン
Node.js 20.x LTS以上
npm 10.x以上
OS Windows/macOS/Linux

前提条件

  • JavaScriptの基礎知識(関数、オブジェクト、async/await)
  • Node.jsの基本API理解
  • コマンドライン操作の基本知識

child_processモジュールの概要

node:child_processモジュールは、Node.jsから子プロセスを生成(spawn)する機能を提供します。Node.jsはシングルスレッドで動作しますが、このモジュールを使用することで外部プログラムを並列に実行できます。

4つの主要APIの比較

API シェル使用 出力形式 主な用途
exec() 常に使用 バッファリング シェルコマンド実行
execFile() デフォルトで不使用 バッファリング 実行ファイル直接実行
spawn() オプション ストリーム 大量出力処理
fork() 不使用 ストリーム+IPC Node.jsプロセス生成

選択フローチャート

flowchart TD
    A[外部プロセス実行] --> B{Node.jsスクリプト?}
    B -->|Yes| C[fork]
    B -->|No| D{シェル機能が必要?}
    D -->|Yes| E{出力サイズ}
    D -->|No| F{出力サイズ}
    E -->|小| G[exec]
    E -->|大| H[spawn + shell: true]
    F -->|小| I[execFile]
    F -->|大| J[spawn]

exec()によるシェルコマンド実行

exec()は最もシンプルなAPIで、シェルを介してコマンドを実行します。出力はバッファリングされ、コールバック関数で一括取得できます。

基本的な使い方

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { exec } from 'node:child_process';

// シェルコマンドを実行
exec('ls -la', (error, stdout, stderr) => {
  if (error) {
    console.error(`実行エラー: ${error.message}`);
    return;
  }
  if (stderr) {
    console.error(`stderr: ${stderr}`);
  }
  console.log(`stdout:\n${stdout}`);
});

Promise版での使用

util.promisify()を使用すると、async/awaitで記述できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { exec } from 'node:child_process';
import { promisify } from 'node:util';

const execAsync = promisify(exec);

async function getNodeVersion() {
  try {
    const { stdout, stderr } = await execAsync('node --version');
    console.log(`Node.jsバージョン: ${stdout.trim()}`);
  } catch (error) {
    console.error(`エラー: ${error.message}`);
  }
}

getNodeVersion();

オプション設定

exec()では様々なオプションを指定できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { exec } from 'node:child_process';

const options = {
  cwd: '/tmp',              // 作業ディレクトリ
  env: { ...process.env, NODE_ENV: 'production' }, // 環境変数
  timeout: 5000,            // タイムアウト(ミリ秒)
  maxBuffer: 1024 * 1024,   // 最大バッファサイズ(デフォルト: 1MB)
  encoding: 'utf8',         // 出力エンコーディング
  shell: '/bin/bash',       // 使用するシェル
};

exec('echo $NODE_ENV', options, (error, stdout) => {
  if (error) {
    console.error(`エラー: ${error.message}`);
    return;
  }
  console.log(`出力: ${stdout}`);
});

シェルメタ文字とセキュリティ

exec()はシェルを介するため、シェルメタ文字が解釈されます。ユーザー入力を直接渡すことは重大なセキュリティリスクになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { exec } from 'node:child_process';

// 危険な例 - 絶対に行わないこと
const userInput = 'file.txt; rm -rf /';  // 悪意のある入力
exec(`cat ${userInput}`, (error, stdout) => {
  // シェルインジェクション攻撃が可能
});

// 安全な代替手段: execFileを使用
import { execFile } from 'node:child_process';
execFile('cat', ['file.txt'], (error, stdout) => {
  // 引数は個別に渡されるため安全
});

execFile()による実行ファイル直接実行

execFile()はシェルを介さずに実行ファイルを直接起動します。シェル機能(パイプ、リダイレクト、ワイルドカード)は使用できませんが、より安全で効率的です。

基本的な使い方

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { execFile } from 'node:child_process';

// 実行ファイルを直接起動
execFile('node', ['--version'], (error, stdout, stderr) => {
  if (error) {
    console.error(`実行エラー: ${error.message}`);
    return;
  }
  console.log(`Node.jsバージョン: ${stdout.trim()}`);
});

Promise版での使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';

const execFileAsync = promisify(execFile);

async function getGitStatus() {
  try {
    const { stdout } = await execFileAsync('git', ['status', '--short']);
    console.log('Git状態:');
    console.log(stdout || '変更なし');
  } catch (error) {
    console.error(`Gitエラー: ${error.message}`);
  }
}

getGitStatus();

execとexecFileの違い

観点 exec() execFile()
シェル 常に使用 デフォルトで不使用
パイプ・リダイレクト 使用可能 使用不可(オプションで可)
ワイルドカード展開 シェルが行う 行われない
セキュリティ 要注意 比較的安全
パフォーマンス シェル起動コスト 直接実行で高速

Windowsでの注意点

Windowsでは.bat.cmdファイルをexecFile()で直接実行できません。shell: trueオプションを使用するか、exec()を使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { execFile, exec } from 'node:child_process';

// Windowsでのバッチファイル実行
// 方法1: shellオプションを有効化
execFile('my-script.bat', [], { shell: true }, (error, stdout) => {
  console.log(stdout);
});

// 方法2: execを使用
exec('my-script.bat', (error, stdout) => {
  console.log(stdout);
});

spawn()によるストリーム処理

spawn()は最も低レベルなAPIで、標準入出力をストリームとして扱います。大量の出力を処理する場合や、リアルタイムで出力を取得したい場合に適しています。

基本的な使い方

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { spawn } from 'node:child_process';

const ls = spawn('ls', ['-la', '/usr']);

// 標準出力をリアルタイムで処理
ls.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

// 標準エラー出力を処理
ls.stderr.on('data', (data) => {
  console.error(`stderr: ${data}`);
});

// プロセス終了時の処理
ls.on('close', (code) => {
  console.log(`プロセス終了(終了コード: ${code})`);
});

// エラーハンドリング
ls.on('error', (error) => {
  console.error(`プロセス起動失敗: ${error.message}`);
});

パイプ処理の実装

spawn()を使うと、シェルのパイプ処理をNode.jsで実装できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { spawn } from 'node:child_process';

// 'ps aux | grep node' 相当の処理
const ps = spawn('ps', ['aux']);
const grep = spawn('grep', ['node']);

// psの出力をgrepの入力に接続
ps.stdout.pipe(grep.stdin);

grep.stdout.on('data', (data) => {
  console.log(`検索結果:\n${data}`);
});

ps.on('close', (code) => {
  if (code !== 0) {
    console.error(`ps終了コード: ${code}`);
  }
});

grep.on('close', (code) => {
  console.log(`grep終了コード: ${code}`);
});

標準入力への書き込み

子プロセスの標準入力にデータを送信できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { spawn } from 'node:child_process';

// catコマンドに標準入力からデータを送信
const cat = spawn('cat');

cat.stdout.on('data', (data) => {
  console.log(`受信: ${data}`);
});

// 標準入力に書き込み
cat.stdin.write('Hello, ');
cat.stdin.write('World!\n');
cat.stdin.end();  // 入力終了を通知

シェルオプションの使用

shell: trueオプションを指定すると、シェル経由で実行できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { spawn } from 'node:child_process';

// シェル機能を使用(パイプ、リダイレクトなど)
const child = spawn('echo $HOME && ls | head -5', {
  shell: true,
  env: process.env,
});

child.stdout.on('data', (data) => {
  console.log(data.toString());
});

stdioオプションの設定

stdioオプションで標準入出力の扱いを制御できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { spawn } from 'node:child_process';

// stdioの設定パターン
// 'pipe': 親プロセスとパイプで接続(デフォルト)
// 'inherit': 親プロセスのstdioを継承
// 'ignore': 無視

// 親プロセスのコンソールを直接使用
const npm = spawn('npm', ['install'], {
  stdio: 'inherit',  // 子プロセスの出力が直接ターミナルに表示
  cwd: './my-project',
});

npm.on('close', (code) => {
  console.log(`npm install完了(終了コード: ${code})`);
});

デタッチドプロセスの作成

親プロセスとは独立して動作する子プロセスを作成できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { spawn } from 'node:child_process';

// 親プロセス終了後も動作し続ける子プロセス
const child = spawn('node', ['long-running-script.js'], {
  detached: true,
  stdio: 'ignore',
});

// 親プロセスが子プロセスの終了を待たないようにする
child.unref();

console.log(`バックグラウンドプロセス起動(PID: ${child.pid})`);

fork()によるNode.jsプロセス生成

fork()はNode.jsプロセスを生成するための特殊なAPIです。親子プロセス間でIPCチャネルが自動的に確立され、メッセージを送受信できます。

基本的な使い方

親プロセス(parent.js):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { fork } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));

// 子プロセスを生成
const child = fork(join(__dirname, 'child.js'));

// 子プロセスからのメッセージを受信
child.on('message', (message) => {
  console.log(`子プロセスから受信: ${JSON.stringify(message)}`);
});

// 子プロセスにメッセージを送信
child.send({ type: 'calculate', data: { a: 10, b: 20 } });

child.on('close', (code) => {
  console.log(`子プロセス終了(終了コード: ${code})`);
});

子プロセス(child.js):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 親プロセスからのメッセージを受信
process.on('message', (message) => {
  console.log(`親プロセスから受信: ${JSON.stringify(message)}`);
  
  if (message.type === 'calculate') {
    const result = message.data.a + message.data.b;
    
    // 親プロセスに結果を送信
    process.send({ type: 'result', data: result });
  }
});

console.log('子プロセス起動完了');

CPUバウンドな処理のオフロード

fork()はCPU負荷の高い処理をメインスレッドから分離するのに適しています。

親プロセス(heavy-computation-parent.js):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { fork } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));

function calculateFibonacci(n) {
  return new Promise((resolve, reject) => {
    const child = fork(join(__dirname, 'fibonacci-worker.js'));
    
    child.on('message', (result) => {
      resolve(result);
      child.kill();
    });
    
    child.on('error', reject);
    
    child.send({ n });
  });
}

async function main() {
  console.log('計算開始...');
  console.log('メインスレッドはブロックされません');
  
  const result = await calculateFibonacci(40);
  console.log(`フィボナッチ(40) = ${result}`);
}

main();

子プロセス(fibonacci-worker.js):

1
2
3
4
5
6
7
8
9
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

process.on('message', ({ n }) => {
  const result = fibonacci(n);
  process.send(result);
});

forkのオプション

1
2
3
4
5
6
7
8
9
import { fork } from 'node:child_process';

const child = fork('./worker.js', ['arg1', 'arg2'], {
  cwd: '/path/to/working/directory',
  env: { ...process.env, WORKER_ID: '1' },
  execArgv: ['--max-old-space-size=4096'],  // Node.jsの起動オプション
  silent: true,  // trueの場合、子プロセスのstdioが親にパイプされる
  serialization: 'advanced',  // 高度なシリアライゼーション(BigInt, Map, Setなどをサポート)
});

高度なシリアライゼーション

serialization: 'advanced'を使用すると、JSONでは表現できないオブジェクトを送信できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { fork } from 'node:child_process';

const child = fork('./worker.js', [], {
  serialization: 'advanced',
});

// Map, Set, BigIntなどを送信可能
child.send({
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3]),
  bigint: 9007199254740993n,
  buffer: Buffer.from('Hello'),
  error: new Error('サンプルエラー'),
});

子プロセスの終了コード取得

子プロセスの終了状態を正確に把握することは、エラーハンドリングにおいて重要です。

イベントによる取得

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { spawn } from 'node:child_process';

const child = spawn('ls', ['/nonexistent']);

// 'exit'イベント: プロセス終了時(stdioはまだ開いている可能性あり)
child.on('exit', (code, signal) => {
  if (signal) {
    console.log(`シグナルで終了: ${signal}`);
  } else {
    console.log(`終了コード: ${code}`);
  }
});

// 'close'イベント: stdioストリームがすべて閉じた後
child.on('close', (code, signal) => {
  console.log('すべてのストリームが閉じました');
});

exitとcloseの違い

イベント タイミング 用途
exit プロセス終了時 終了コード/シグナルの取得
close stdio閉鎖後 すべての出力処理完了後の後処理

終了コードの意味

コード 意味
0 正常終了
1 一般的なエラー
2 シェルコマンドの誤用
126 コマンド実行不可
127 コマンドが見つからない
128+N シグナルNで終了

同期APIでの終了コード取得

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { spawnSync } from 'node:child_process';

const result = spawnSync('ls', ['-la']);

console.log(`終了コード: ${result.status}`);
console.log(`終了シグナル: ${result.signal}`);
console.log(`stdout: ${result.stdout?.toString()}`);
console.log(`stderr: ${result.stderr?.toString()}`);

if (result.error) {
  console.error(`エラー: ${result.error.message}`);
}

同期APIと非同期APIの使い分け

child_processモジュールには同期版のAPIも用意されています。

同期APIの種類

非同期API 同期API
exec() execSync()
execFile() execFileSync()
spawn() spawnSync()

同期APIの使用例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { execSync, spawnSync } from 'node:child_process';

// execSync: シンプルな同期実行
try {
  const stdout = execSync('node --version', { encoding: 'utf8' });
  console.log(`Node.jsバージョン: ${stdout.trim()}`);
} catch (error) {
  console.error(`エラー: ${error.message}`);
}

// spawnSync: より詳細な制御
const result = spawnSync('git', ['status', '--short'], {
  encoding: 'utf8',
  cwd: process.cwd(),
});

if (result.status === 0) {
  console.log(result.stdout || '変更なし');
} else {
  console.error(result.stderr);
}

使い分けの指針

状況 推奨API
CLIツール、スクリプト 同期API可
Webサーバー 非同期API必須
起動時の設定読み込み 同期API可
リクエスト処理中 非同期API必須
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { execSync, exec } from 'node:child_process';
import { promisify } from 'node:util';

const execAsync = promisify(exec);

// CLIツールでの使用例(同期APIが適切)
function getGitBranch() {
  try {
    return execSync('git rev-parse --abbrev-ref HEAD', {
      encoding: 'utf8',
    }).trim();
  } catch {
    return 'unknown';
  }
}

// Webサーバーでの使用例(非同期APIが必須)
async function handleRequest(req, res) {
  // 同期APIを使うとイベントループがブロックされる
  const { stdout } = await execAsync('some-command');
  res.send(stdout);
}

AbortControllerによるプロセスの中断

Node.js 15以降では、AbortControllerを使用してプロセスを中断できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { spawn, exec } from 'node:child_process';

// spawnでの使用
const controller = new AbortController();
const { signal } = controller;

const child = spawn('sleep', ['100'], { signal });

child.on('error', (error) => {
  if (error.name === 'AbortError') {
    console.log('プロセスが中断されました');
  } else {
    console.error(`エラー: ${error.message}`);
  }
});

// 3秒後に中断
setTimeout(() => {
  controller.abort();
}, 3000);

タイムアウト処理との組み合わせ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { spawn } from 'node:child_process';

function runWithTimeout(command, args, timeoutMs) {
  return new Promise((resolve, reject) => {
    const controller = new AbortController();
    const { signal } = controller;
    
    const timeoutId = setTimeout(() => {
      controller.abort();
    }, timeoutMs);
    
    const child = spawn(command, args, { signal });
    let stdout = '';
    let stderr = '';
    
    child.stdout.on('data', (data) => {
      stdout += data;
    });
    
    child.stderr.on('data', (data) => {
      stderr += data;
    });
    
    child.on('close', (code) => {
      clearTimeout(timeoutId);
      resolve({ code, stdout, stderr });
    });
    
    child.on('error', (error) => {
      clearTimeout(timeoutId);
      if (error.name === 'AbortError') {
        reject(new Error(`タイムアウト(${timeoutMs}ms)`));
      } else {
        reject(error);
      }
    });
  });
}

// 使用例
try {
  const result = await runWithTimeout('sleep', ['10'], 3000);
  console.log(result);
} catch (error) {
  console.error(error.message);
}

実践的なユースケース

npmスクリプトの実行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { spawn } from 'node:child_process';

function runNpmScript(scriptName, cwd = process.cwd()) {
  return new Promise((resolve, reject) => {
    const npm = spawn('npm', ['run', scriptName], {
      cwd,
      stdio: 'inherit',
      shell: process.platform === 'win32',  // Windowsでは必要
    });
    
    npm.on('close', (code) => {
      if (code === 0) {
        resolve();
      } else {
        reject(new Error(`npm run ${scriptName} が終了コード ${code} で失敗`));
      }
    });
    
    npm.on('error', reject);
  });
}

// 使用例
await runNpmScript('build');
await runNpmScript('test');

Git操作の自動化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';

const execFileAsync = promisify(execFile);

class GitHelper {
  constructor(repoPath) {
    this.repoPath = repoPath;
    this.options = { cwd: repoPath };
  }
  
  async getCurrentBranch() {
    const { stdout } = await execFileAsync(
      'git', ['rev-parse', '--abbrev-ref', 'HEAD'],
      this.options
    );
    return stdout.trim();
  }
  
  async getStatus() {
    const { stdout } = await execFileAsync(
      'git', ['status', '--porcelain'],
      this.options
    );
    return stdout.split('\n').filter(Boolean);
  }
  
  async commit(message) {
    await execFileAsync('git', ['add', '-A'], this.options);
    await execFileAsync('git', ['commit', '-m', message], this.options);
  }
  
  async push(remote = 'origin', branch) {
    const currentBranch = branch || await this.getCurrentBranch();
    await execFileAsync(
      'git', ['push', remote, currentBranch],
      this.options
    );
  }
}

// 使用例
const git = new GitHelper('/path/to/repo');
console.log(`現在のブランチ: ${await git.getCurrentBranch()}`);

複数コマンドの直列/並列実行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { exec } from 'node:child_process';
import { promisify } from 'node:util';

const execAsync = promisify(exec);

// 直列実行
async function runSequential(commands) {
  const results = [];
  for (const cmd of commands) {
    const result = await execAsync(cmd);
    results.push(result);
  }
  return results;
}

// 並列実行
async function runParallel(commands) {
  return Promise.all(commands.map(cmd => execAsync(cmd)));
}

// 使用例
const commands = [
  'echo "Task 1"',
  'echo "Task 2"',
  'echo "Task 3"',
];

// 直列実行
console.log('直列実行:');
await runSequential(commands);

// 並列実行
console.log('並列実行:');
await runParallel(commands);

エラーハンドリングのベストプラクティス

包括的なエラーハンドリング

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { spawn } from 'node:child_process';

function executeCommand(command, args, options = {}) {
  return new Promise((resolve, reject) => {
    const child = spawn(command, args, options);
    
    let stdout = '';
    let stderr = '';
    
    if (child.stdout) {
      child.stdout.on('data', (data) => {
        stdout += data;
      });
    }
    
    if (child.stderr) {
      child.stderr.on('data', (data) => {
        stderr += data;
      });
    }
    
    // プロセス起動失敗
    child.on('error', (error) => {
      reject(new Error(`プロセス起動失敗: ${error.message}`));
    });
    
    child.on('close', (code, signal) => {
      if (signal) {
        reject(new Error(`シグナル ${signal} で終了`));
        return;
      }
      
      if (code !== 0) {
        const error = new Error(`終了コード ${code} で失敗`);
        error.code = code;
        error.stdout = stdout;
        error.stderr = stderr;
        reject(error);
        return;
      }
      
      resolve({ stdout, stderr, code });
    });
  });
}

// 使用例
try {
  const result = await executeCommand('ls', ['-la', '/nonexistent']);
  console.log(result.stdout);
} catch (error) {
  console.error(`エラー: ${error.message}`);
  if (error.stderr) {
    console.error(`stderr: ${error.stderr}`);
  }
}

リトライロジックの実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
async function executeWithRetry(command, args, options = {}, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await executeCommand(command, args, options);
    } catch (error) {
      lastError = error;
      console.warn(`試行 ${attempt}/${maxRetries} 失敗: ${error.message}`);
      
      if (attempt < maxRetries) {
        // 指数バックオフで待機
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  throw lastError;
}

まとめ

node:child_processモジュールは、Node.jsから外部プロセスを実行するための強力な機能を提供します。

  • exec(): シェルコマンドを手軽に実行(出力が少ない場合)
  • execFile(): 実行ファイルを安全に直接実行(シェル機能不要時)
  • spawn(): ストリームで大量出力を処理(リアルタイム処理)
  • fork(): Node.jsプロセス間でIPC通信(CPU負荷分散)

適切なAPIを選択し、セキュリティ(特にシェルインジェクション対策)とエラーハンドリングを意識することで、外部プログラムとの連携を安全かつ効率的に実装できます。

参考リンク