子进程#

稳定性:2 - 稳定

源代码: lib/child_process.js

node:child_process 模块提供了以类似于 popen(3) 但不完全相同的方式衍生子进程的能力。这个功能主要由 child_process.spawn() 函数提供。

const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/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(`child process exited with code ${code}`);
});import { spawn } from 'node:child_process';
const ls = spawn('ls', ['-lh', '/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(`child process exited with code ${code}`);
});

默认情况下,父 Node.js 进程和衍生的子进程之间会建立用于 stdinstdoutstderr 的管道。这些管道的容量有限(且因平台而异)。如果子进程向 stdout 写入的数据超出了该限制而输出未被捕获,子进程将阻塞,等待管道缓冲区接受更多数据。这与 shell 中管道的行为相同。如果输出不会被消费,请使用 { stdio: 'ignore' } 选项。

如果 options 对象中存在 env,则使用 options.env.PATH 环境变量进行命令查找。否则,使用 process.env.PATH。如果在设置 options.env 时没有设置 PATH,则在 Unix 上会在默认搜索路径 /usr/bin:/bin 上执行查找(请参阅您操作系统的 execvpe/execvp 手册),在 Windows 上则使用当前进程的环境变量 PATH

在 Windows 上,环境变量不区分大小写。Node.js 会按字典顺序对 env 的键进行排序,并使用第一个不区分大小写匹配的键。只有(按字典顺序)第一个条目会被传递给子进程。当向 env 选项传递包含同一键的多个变体(如 PATHPath)的对象时,这可能会在 Windows 上导致问题。

child_process.spawn() 方法异步地衍生子进程,不会阻塞 Node.js 事件循环。child_process.spawnSync() 函数以同步方式提供了等效的功能,它会阻塞事件循环,直到衍生的进程退出或被终止。

为方便起见,node:child_process 模块提供了几个同步和异步的替代方案来替代 child_process.spawn()child_process.spawnSync()。这些替代方案都是在 child_process.spawn()child_process.spawnSync() 的基础上实现的。

对于某些用例,例如自动化 shell 脚本,同步的对应方法可能更方便。然而,在许多情况下,同步方法可能会对性能产生重大影响,因为它们会在衍生进程完成时阻塞事件循环。

异步进程创建#

child_process.spawn()child_process.fork()child_process.exec()child_process.execFile() 方法都遵循其他 Node.js API 中典型的异步编程模式。

每个方法都返回一个 ChildProcess 实例。这些对象实现了 Node.js 的 EventEmitter API,允许父进程注册监听函数,这些函数在子进程生命周期中发生特定事件时被调用。

child_process.exec()child_process.execFile() 方法还允许指定一个可选的 callback 函数,该函数在子进程终止时被调用。

在 Windows 上衍生 .bat.cmd 文件#

child_process.exec()child_process.execFile() 之间的区别的重要性可能因平台而异。在类 Unix 操作系统(Unix、Linux、macOS)上,child_process.execFile() 可能更高效,因为它默认不衍生 shell。然而,在 Windows 上,.bat.cmd 文件本身没有终端是无法执行的,因此不能使用 child_process.execFile() 启动。在 Windows 上运行时,.bat.cmd 文件可以使用设置了 shell 选项的 child_process.spawn()child_process.exec(),或者通过衍生 cmd.exe 并将 .bat.cmd 文件作为参数传递来调用(shell 选项和 child_process.exec() 就是这么做的)。在任何情况下,如果脚本文件名包含空格,都需要用引号引起来。

// OR...
const { exec, spawn } = require('node:child_process');

exec('my.bat', (err, stdout, stderr) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(stdout);
});

// Script with spaces in the filename:
const bat = spawn('"my script.cmd" a b', { shell: true });
// or:
exec('"my script.cmd" a b', (err, stdout, stderr) => {
  // ...
});// OR...
import { exec, spawn } from 'node:child_process';

exec('my.bat', (err, stdout, stderr) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(stdout);
});

// Script with spaces in the filename:
const bat = spawn('"my script.cmd" a b', { shell: true });
// or:
exec('"my script.cmd" a b', (err, stdout, stderr) => {
  // ...
});

child_process.exec(command[, options][, callback])#

衍生一个 shell,然后在该 shell 中执行 command,并缓冲任何生成的输出。传递给 exec 函数的 command 字符串由 shell 直接处理,特殊字符(因 shell 而异)需要相应处理。

const { exec } = require('node:child_process');

exec('"/path/to/test file/test.sh" arg1 arg2');
// Double quotes are used so that the space in the path is not interpreted as
// a delimiter of multiple arguments.

exec('echo "The \\$HOME variable is $HOME"');
// The $HOME variable is escaped in the first instance, but not in the second.import { exec } from 'node:child_process';

exec('"/path/to/test file/test.sh" arg1 arg2');
// Double quotes are used so that the space in the path is not interpreted as
// a delimiter of multiple arguments.

exec('echo "The \\$HOME variable is $HOME"');
// The $HOME variable is escaped in the first instance, but not in the second.

切勿将未经处理的用户输入传递给此函数。任何包含 shell 元字符的输入都可能被用于触发任意命令执行。

如果提供了 callback 函数,它会以 (error, stdout, stderr) 作为参数被调用。成功时,error 将为 null。出错时,error 将是 Error 的一个实例。error.code 属性将是进程的退出码。按照惯例,任何非 0 的退出码都表示错误。error.signal 将是终止进程的信号。

传递给回调函数的 stdoutstderr 参数将包含子进程的 stdout 和 stderr 输出。默认情况下,Node.js 会将输出解码为 UTF-8 并将字符串传递给回调函数。encoding 选项可用于指定用于解码 stdout 和 stderr 输出的字符编码。如果 encoding'buffer' 或无法识别的字符编码,则会向回调函数传递 Buffer 对象。

const { exec } = require('node:child_process');
exec('cat *.js missing_file | wc -l', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.error(`stderr: ${stderr}`);
});import { exec } from 'node:child_process';
exec('cat *.js missing_file | wc -l', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.error(`stderr: ${stderr}`);
});

如果 timeout 大于 0,当子进程运行时间超过 timeout 毫秒时,父进程将发送由 killSignal 属性标识的信号(默认为 'SIGTERM')。

exec(3) POSIX 系统调用不同,child_process.exec() 不会替换现有进程,而是使用 shell 来执行命令。

如果此方法作为其 util.promisify()ed 版本被调用,它会返回一个解析为带有 stdoutstderr 属性的 ObjectPromise。返回的 ChildProcess 实例作为 child 属性附加到 Promise 上。如果发生错误(包括任何导致退出码不为 0 的错误),将返回一个被拒绝的 promise,其 error 对象与回调函数中的相同,但额外增加了 stdoutstderr 两个属性。

const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);

async function lsExample() {
  const { stdout, stderr } = await exec('ls');
  console.log('stdout:', stdout);
  console.error('stderr:', stderr);
}
lsExample();import { promisify } from 'node:util';
import child_process from 'node:child_process';
const exec = promisify(child_process.exec);

async function lsExample() {
  const { stdout, stderr } = await exec('ls');
  console.log('stdout:', stdout);
  console.error('stderr:', stderr);
}
lsExample();

如果启用了 signal 选项,在相应的 AbortController 上调用 .abort() 类似于在子进程上调用 .kill(),只是传递给回调的错误将是一个 AbortError

const { exec } = require('node:child_process');
const controller = new AbortController();
const { signal } = controller;
const child = exec('grep ssh', { signal }, (error) => {
  console.error(error); // an AbortError
});
controller.abort();import { exec } from 'node:child_process';
const controller = new AbortController();
const { signal } = controller;
const child = exec('grep ssh', { signal }, (error) => {
  console.error(error); // an AbortError
});
controller.abort();

child_process.execFile(file[, args][, options][, callback])#

  • file <string> 要运行的可执行文件的名称或路径。
  • args <string[]> 字符串参数列表。
  • options <Object>
    • cwd <string> | <URL> 子进程的当前工作目录。
    • env <Object> 环境变量键值对。默认值: process.env
    • encoding <string> 默认值: 'utf8'
    • timeout <number> 默认值: 0
    • maxBuffer <number> stdout 或 stderr 上允许的最大数据量(以字节为单位)。如果超出,子进程将被终止,任何输出都将被截断。请参阅 maxBuffer 与 Unicode 中的注意事项。默认值: 1024 * 1024
    • killSignal <string> | <integer> 默认值: 'SIGTERM'
    • uid <number> 设置进程的用户标识(参见 setuid(2))。
    • gid <number> 设置进程的组标识(参见 setgid(2))。
    • windowsHide <boolean> 隐藏通常在 Windows 系统上创建的子进程控制台窗口。默认值: false
    • windowsVerbatimArguments <boolean> 在 Windows 上不对参数进行引用或转义。在 Unix 上被忽略。默认值: false
    • shell <boolean> | <string> 如果为 true,则在 shell 中运行 command。在 Unix 上使用 '/bin/sh',在 Windows 上使用 process.env.ComSpec。可以指定一个不同的 shell 作为字符串。请参阅Shell 要求默认 Windows shell默认值: false (无 shell)。
    • signal <AbortSignal> 允许使用 AbortSignal 中止子进程。
  • callback <Function> 在进程终止时使用输出调用。
  • 返回:<ChildProcess>

child_process.execFile() 函数与 child_process.exec() 类似,不同之处在于它默认不衍生 shell。相反,指定的可执行文件 file 被直接衍生为一个新进程,这使得它比 child_process.exec() 稍微高效一些。

支持与 child_process.exec() 相同的选项。由于不衍生 shell,因此不支持 I/O 重定向和文件通配等行为。

const { execFile } = require('node:child_process');
const child = execFile('node', ['--version'], (error, stdout, stderr) => {
  if (error) {
    throw error;
  }
  console.log(stdout);
});import { execFile } from 'node:child_process';
const child = execFile('node', ['--version'], (error, stdout, stderr) => {
  if (error) {
    throw error;
  }
  console.log(stdout);
});

传递给回调函数的 stdoutstderr 参数将包含子进程的 stdout 和 stderr 输出。默认情况下,Node.js 会将输出解码为 UTF-8 并将字符串传递给回调函数。encoding 选项可用于指定用于解码 stdout 和 stderr 输出的字符编码。如果 encoding'buffer' 或无法识别的字符编码,则会向回调函数传递 Buffer 对象。

如果此方法作为其 util.promisify()ed 版本被调用,它会返回一个解析为带有 stdoutstderr 属性的 ObjectPromise。返回的 ChildProcess 实例作为 child 属性附加到 Promise 上。如果发生错误(包括任何导致退出码不为 0 的错误),将返回一个被拒绝的 promise,其 error 对象与回调函数中的相同,但额外增加了 stdoutstderr 两个属性。

const util = require('node:util');
const execFile = util.promisify(require('node:child_process').execFile);
async function getVersion() {
  const { stdout } = await execFile('node', ['--version']);
  console.log(stdout);
}
getVersion();import { promisify } from 'node:util';
import child_process from 'node:child_process';
const execFile = promisify(child_process.execFile);
async function getVersion() {
  const { stdout } = await execFile('node', ['--version']);
  console.log(stdout);
}
getVersion();

如果启用了 shell 选项,切勿将未经处理的用户输入传递给此函数。任何包含 shell 元字符的输入都可能被用于触发任意命令执行。

如果启用了 signal 选项,在相应的 AbortController 上调用 .abort() 类似于在子进程上调用 .kill(),只是传递给回调的错误将是一个 AbortError

const { execFile } = require('node:child_process');
const controller = new AbortController();
const { signal } = controller;
const child = execFile('node', ['--version'], { signal }, (error) => {
  console.error(error); // an AbortError
});
controller.abort();import { execFile } from 'node:child_process';
const controller = new AbortController();
const { signal } = controller;
const child = execFile('node', ['--version'], { signal }, (error) => {
  console.error(error); // an AbortError
});
controller.abort();

child_process.fork(modulePath[, args][, options])#

  • modulePath <string> | <URL> 在子进程中运行的模块。
  • args <string[]> 字符串参数列表。
  • options <Object>
    • cwd <string> | <URL> 子进程的当前工作目录。
    • detached <boolean> 准备子进程以独立于其父进程运行。具体行为取决于平台(参见 options.detached)。
    • env <Object> 环境变量键值对。默认值: process.env
    • execPath <string> 用于创建子进程的可执行文件。
    • execArgv <string[]> 传递给可执行文件的字符串参数列表。默认值: process.execArgv
    • gid <number> 设置进程的组标识(参见 setgid(2))。
    • serialization <string> 指定用于在进程间发送消息的序列化类型。可能的值为 'json''advanced'。更多详情请参见高级序列化默认值: 'json'
    • signal <AbortSignal> 允许使用 AbortSignal 关闭子进程。
    • killSignal <string> | <integer> 当衍生的进程因超时或中止信号被杀死时使用的信号值。默认值: 'SIGTERM'
    • silent <boolean> 如果为 true,子进程的 stdin、stdout 和 stderr 将被管道传输到父进程,否则它们将从父进程继承,更多详情请参见 child_process.spawn()stdio'pipe''inherit' 选项。默认值: false
    • stdio <Array> | <string> 参见 child_process.spawn()stdio。当提供此选项时,它会覆盖 silent。如果使用数组变体,它必须只包含一个值为 'ipc' 的项,否则将抛出错误。例如 [0, 1, 2, 'ipc']
    • uid <number> 设置进程的用户标识(参见 setuid(2))。
    • windowsVerbatimArguments <boolean> 在 Windows 上不对参数进行引用或转义。在 Unix 上被忽略。默认值: false
    • timeout <number> 进程被允许运行的最长时间(以毫秒为单位)。默认值: undefined
  • 返回:<ChildProcess>

child_process.fork() 方法是 child_process.spawn() 的一个特例,专门用于衍生新的 Node.js 进程。与 child_process.spawn() 一样,返回一个 ChildProcess 对象。返回的 ChildProcess 将有一个内置的额外通信通道,允许在父子进程之间来回传递消息。详情请参见 subprocess.send()

请记住,衍生的 Node.js 子进程独立于父进程,除了两者之间建立的 IPC 通信通道。每个进程都有自己的内存和自己的 V8 实例。由于需要额外的资源分配,不建议衍生大量的子 Node.js 进程。

默认情况下,child_process.fork() 将使用父进程的 process.execPath 来衍生新的 Node.js 实例。options 对象中的 execPath 属性允许使用替代的执行路径。

使用自定义 execPath 启动的 Node.js 进程将使用环境变量 NODE_CHANNEL_FD 在子进程上标识的文件描述符(fd)与父进程通信。

fork(2) POSIX 系统调用不同,child_process.fork() 不会克隆当前进程。

child_process.fork() 不支持 child_process.spawn() 中可用的 shell 选项,如果设置了该选项将被忽略。

如果启用了 signal 选项,在相应的 AbortController 上调用 .abort() 类似于在子进程上调用 .kill(),只是传递给回调的错误将是一个 AbortError

const { fork } = require('node:child_process');
const process = require('node:process');

if (process.argv[2] === 'child') {
  setTimeout(() => {
    console.log(`Hello from ${process.argv[2]}!`);
  }, 1_000);
} else {
  const controller = new AbortController();
  const { signal } = controller;
  const child = fork(__filename, ['child'], { signal });
  child.on('error', (err) => {
    // This will be called with err being an AbortError if the controller aborts
  });
  controller.abort(); // Stops the child process
}import { fork } from 'node:child_process';
import process from 'node:process';

if (process.argv[2] === 'child') {
  setTimeout(() => {
    console.log(`Hello from ${process.argv[2]}!`);
  }, 1_000);
} else {
  const controller = new AbortController();
  const { signal } = controller;
  const child = fork(import.meta.url, ['child'], { signal });
  child.on('error', (err) => {
    // This will be called with err being an AbortError if the controller aborts
  });
  controller.abort(); // Stops the child process
}

child_process.spawn(command[, args][, options])#

  • command <string> 要运行的命令。
  • args <string[]> 字符串参数列表。
  • options <Object>
    • cwd <string> | <URL> 子进程的当前工作目录。
    • env <Object> 环境变量键值对。默认值: process.env
    • argv0 <string> 显式设置发送给子进程的 argv[0] 的值。如果未指定,将设置为 command
    • stdio <Array> | <string> 子进程的 stdio 配置(参见 options.stdio)。
    • detached <boolean> 准备子进程以独立于其父进程运行。具体行为取决于平台(参见 options.detached)。
    • uid <number> 设置进程的用户标识(参见 setuid(2))。
    • gid <number> 设置进程的组标识(参见 setgid(2))。
    • serialization <string> 指定用于在进程间发送消息的序列化类型。可能的值为 'json''advanced'。更多详情请参见高级序列化默认值: 'json'
    • shell <boolean> | <string> 如果为 true,则在 shell 中运行 command。在 Unix 上使用 '/bin/sh',在 Windows 上使用 process.env.ComSpec。可以指定一个不同的 shell 作为字符串。请参阅Shell 要求默认 Windows shell默认值: false (无 shell)。
    • windowsVerbatimArguments <boolean> 在 Windows 上不对参数进行引用或转义。在 Unix 上被忽略。当指定 shell 并且是 CMD 时,此选项会自动设置为 true默认值: false
    • windowsHide <boolean> 隐藏通常在 Windows 系统上创建的子进程控制台窗口。默认值: false
    • signal <AbortSignal> 允许使用 AbortSignal 中止子进程。
    • timeout <number> 进程被允许运行的最长时间(以毫秒为单位)。默认值: undefined
    • killSignal <string> | <integer> 当衍生的进程因超时或中止信号被杀死时使用的信号值。默认值: 'SIGTERM'
  • 返回:<ChildProcess>

child_process.spawn() 方法使用给定的 command 衍生一个新进程,命令行参数在 args 中。如果省略,args 默认为一个空数组。

如果启用了 shell 选项,切勿将未经处理的用户输入传递给此函数。任何包含 shell 元字符的输入都可能被用于触发任意命令执行。

第三个参数可用于指定附加选项,默认值如下:

const defaults = {
  cwd: undefined,
  env: process.env,
}; 

使用 cwd 指定衍生进程的工作目录。如果未给出,则默认继承当前工作目录。如果给出,但路径不存在,子进程会发出一个 ENOENT 错误并立即退出。当命令不存在时也会发出 ENOENT

使用 env 指定对新进程可见的环境变量,默认为 process.env

env 中的 undefined 值将被忽略。

运行 ls -lh /usr,捕获 stdoutstderr 和退出码的示例:

const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/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(`child process exited with code ${code}`);
});import { spawn } from 'node:child_process';
const ls = spawn('ls', ['-lh', '/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(`child process exited with code ${code}`);
});

示例:一种非常复杂的方式来运行 ps ax | grep ssh

const { spawn } = require('node:child_process');
const ps = spawn('ps', ['ax']);
const grep = spawn('grep', ['ssh']);

ps.stdout.on('data', (data) => {
  grep.stdin.write(data);
});

ps.stderr.on('data', (data) => {
  console.error(`ps stderr: ${data}`);
});

ps.on('close', (code) => {
  if (code !== 0) {
    console.log(`ps process exited with code ${code}`);
  }
  grep.stdin.end();
});

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

grep.stderr.on('data', (data) => {
  console.error(`grep stderr: ${data}`);
});

grep.on('close', (code) => {
  if (code !== 0) {
    console.log(`grep process exited with code ${code}`);
  }
});import { spawn } from 'node:child_process';
const ps = spawn('ps', ['ax']);
const grep = spawn('grep', ['ssh']);

ps.stdout.on('data', (data) => {
  grep.stdin.write(data);
});

ps.stderr.on('data', (data) => {
  console.error(`ps stderr: ${data}`);
});

ps.on('close', (code) => {
  if (code !== 0) {
    console.log(`ps process exited with code ${code}`);
  }
  grep.stdin.end();
});

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

grep.stderr.on('data', (data) => {
  console.error(`grep stderr: ${data}`);
});

grep.on('close', (code) => {
  if (code !== 0) {
    console.log(`grep process exited with code ${code}`);
  }
});

检查 spawn 失败的示例:

const { spawn } = require('node:child_process');
const subprocess = spawn('bad_command');

subprocess.on('error', (err) => {
  console.error('Failed to start subprocess.');
});import { spawn } from 'node:child_process';
const subprocess = spawn('bad_command');

subprocess.on('error', (err) => {
  console.error('Failed to start subprocess.');
});

某些平台(macOS、Linux)将使用 argv[0] 的值作为进程标题,而其他平台(Windows、SunOS)将使用 command

Node.js 在启动时会用 process.execPath 覆盖 argv[0],因此 Node.js 子进程中的 process.argv[0] 将与从父进程传递给 spawnargv0 参数不匹配。请改用 process.argv0 属性来检索它。

如果启用了 signal 选项,在相应的 AbortController 上调用 .abort() 类似于在子进程上调用 .kill(),只是传递给回调的错误将是一个 AbortError

const { spawn } = require('node:child_process');
const controller = new AbortController();
const { signal } = controller;
const grep = spawn('grep', ['ssh'], { signal });
grep.on('error', (err) => {
  // This will be called with err being an AbortError if the controller aborts
});
controller.abort(); // Stops the child processimport { spawn } from 'node:child_process';
const controller = new AbortController();
const { signal } = controller;
const grep = spawn('grep', ['ssh'], { signal });
grep.on('error', (err) => {
  // This will be called with err being an AbortError if the controller aborts
});
controller.abort(); // Stops the child process
options.detached#

在 Windows 上,将 options.detached 设置为 true 使得子进程可以在父进程退出后继续运行。子进程将有自己的控制台窗口。一旦为子进程启用,便无法禁用。

在非 Windows 平台上,如果 options.detached 设置为 true,子进程将成为新进程组和会话的领导者。无论是否分离,子进程都可以在父进程退出后继续运行。更多信息请参见 setsid(2)

默认情况下,父进程将等待分离的子进程退出。要防止父进程等待给定的 subprocess 退出,请使用 subprocess.unref() 方法。这样做将导致父进程的事件循环不将子进程包含在其引用计数中,从而允许父进程独立于子进程退出,除非子进程和父进程之间建立了 IPC 通道。

当使用 detached 选项启动一个长时间运行的进程时,除非为其提供一个未连接到父进程的 stdio 配置,否则该进程在父进程退出后不会在后台继续运行。如果继承了父进程的 stdio,子进程将保持附加到控制终端。

一个长时间运行进程的示例,通过分离并忽略其父进程的 stdio 文件描述符,以忽略父进程的终止:

const { spawn } = require('node:child_process');
const process = require('node:process');

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();import { spawn } from 'node:child_process';
import process from 'node:process';

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();

或者,可以将子进程的输出重定向到文件中:

const { openSync } = require('node:fs');
const { spawn } = require('node:child_process');
const out = openSync('./out.log', 'a');
const err = openSync('./out.log', 'a');

const subprocess = spawn('prg', [], {
  detached: true,
  stdio: [ 'ignore', out, err ],
});

subprocess.unref();import { openSync } from 'node:fs';
import { spawn } from 'node:child_process';
const out = openSync('./out.log', 'a');
const err = openSync('./out.log', 'a');

const subprocess = spawn('prg', [], {
  detached: true,
  stdio: [ 'ignore', out, err ],
});

subprocess.unref();
options.stdio#

options.stdio 选项用于配置在父进程和子进程之间建立的管道。默认情况下,子进程的 stdin、stdout 和 stderr 会被重定向到 ChildProcess 对象上相应的 subprocess.stdinsubprocess.stdoutsubprocess.stderr 流。这等同于将 options.stdio 设置为 ['pipe', 'pipe', 'pipe']

为方便起见,options.stdio 可以是以下字符串之一:

  • 'pipe':等同于 ['pipe', 'pipe', 'pipe'] (默认值)
  • 'overlapped':等同于 ['overlapped', 'overlapped', 'overlapped']
  • 'ignore':等同于 ['ignore', 'ignore', 'ignore']
  • 'inherit':等同于 ['inherit', 'inherit', 'inherit'][0, 1, 2]

否则,options.stdio 的值是一个数组,其中每个索引对应于子进程中的一个 fd。fd 0、1 和 2 分别对应于 stdin、stdout 和 stderr。可以指定额外的 fd 以在父子进程之间创建额外的管道。该值为以下之一:

  1. 'pipe':在子进程和父进程之间创建一个管道。管道的父端作为 child_process 对象上的一个属性,即 subprocess.stdio[fd],暴露给父进程。为 fd 0、1 和 2 创建的管道也分别作为 subprocess.stdinsubprocess.stdoutsubprocess.stderr 可用。这些不是真正的 Unix 管道,因此子进程不能通过其描述符文件(例如 /dev/fd/2/dev/stdout)来使用它们。

  2. 'overlapped':与 'pipe' 相同,只是在句柄上设置了 FILE_FLAG_OVERLAPPED 标志。这对于子进程的 stdio 句柄上的重叠 I/O 是必需的。更多详情请参阅文档。在非 Windows 系统上,这与 'pipe' 完全相同。

  3. 'ipc':创建一个 IPC 通道,用于在父子进程间传递消息/文件描述符。一个 ChildProcess 最多只能有一个 IPC stdio 文件描述符。设置此选项会启用 subprocess.send() 方法。如果子进程是一个 Node.js 实例,IPC 通道的存在将启用 process.send()process.disconnect() 方法,以及子进程内的 'disconnect''message' 事件。

    不支持以 process.send() 以外的任何方式访问 IPC 通道 fd,或将 IPC 通道用于非 Node.js 实例的子进程。

  4. 'ignore':指示 Node.js 忽略子进程中的 fd。虽然 Node.js 总是为其衍生的进程打开 fd 0、1 和 2,但将 fd 设置为 'ignore' 会导致 Node.js 打开 /dev/null 并将其附加到子进程的 fd 上。

  5. 'inherit':将相应的 stdio 流传递给/从父进程。在前三个位置,这分别等同于 process.stdinprocess.stdoutprocess.stderr。在任何其他位置,等同于 'ignore'

  6. <Stream> 对象:与子进程共享一个可读或可写的流,该流引用一个 tty、文件、套接字或管道。该流的底层文件描述符在子进程中被复制到与 stdio 数组中索引对应的 fd。该流必须有一个底层描述符(文件流直到 'open' 事件发生后才开始)。注意: 虽然技术上可以将 stdin 作为可写流或 stdout/stderr 作为可读流传递,但不建议这样做。可读流和可写流设计有不同的行为,不正确地使用它们(例如,在需要可写流的地方传递可读流)可能导致意外结果或错误。不鼓励这种做法,因为它可能导致未定义的行为或在流遇到错误时丢失回调。始终确保 stdin 作为可读流使用,stdout/stderr 作为可写流使用,以维持父子进程之间数据的预期流动。

  7. 正整数:该整数值被解释为在父进程中打开的文件描述符。它与子进程共享,类似于 <Stream> 对象的共享方式。在 Windows 上不支持传递套接字。

  8. nullundefined:使用默认值。对于 stdio fd 0、1 和 2(即 stdin、stdout 和 stderr),会创建一个管道。对于 fd 3 及以上,默认为 'ignore'

const { spawn } = require('node:child_process');
const process = require('node:process');

// Child will use parent's stdios.
spawn('prg', [], { stdio: 'inherit' });

// Spawn child sharing only stderr.
spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] });

// Open an extra fd=4, to interact with programs presenting a
// startd-style interface.
spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });import { spawn } from 'node:child_process';
import process from 'node:process';

// Child will use parent's stdios.
spawn('prg', [], { stdio: 'inherit' });

// Spawn child sharing only stderr.
spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] });

// Open an extra fd=4, to interact with programs presenting a
// startd-style interface.
spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });

值得注意的是,当父子进程之间建立 IPC 通道,并且子进程是一个 Node.js 实例时,子进程启动时 IPC 通道是未引用的(使用 unref()),直到子进程为 'disconnect' 事件或 'message' 事件注册事件处理程序。这允许子进程正常退出,而不会因为打开的 IPC 通道而使进程保持打开状态。 另见:child_process.exec()child_process.fork()

同步进程创建#

child_process.spawnSync()child_process.execSync()child_process.execFileSync() 方法是同步的,会阻塞 Node.js 事件循环,暂停执行任何其他代码,直到衍生的进程退出。

这类阻塞调用主要用于简化通用脚本任务和简化启动时应用程序配置的加载/处理。

child_process.execFileSync(file[, args][, options])#

  • file <string> 要运行的可执行文件的名称或路径。
  • args <string[]> 字符串参数列表。
  • options <Object>
    • cwd <string> | <URL> 子进程的当前工作目录。
    • input <string> | <Buffer> | <TypedArray> | <DataView> 将作为 stdin 传递给衍生进程的值。如果 stdio[0] 设置为 'pipe',提供此值将覆盖 stdio[0]
    • stdio <string> | <Array> 子进程的 stdio 配置。参见 child_process.spawn()stdio。默认情况下,stderr 将输出到父进程的 stderr,除非指定了 stdio默认值: 'pipe'
    • env <Object> 环境变量键值对。默认值: process.env
    • uid <number> 设置进程的用户标识(参见 setuid(2))。
    • gid <number> 设置进程的组标识(参见 setgid(2))。
    • timeout <number> 进程被允许运行的最长时间(以毫秒为单位)。默认值: undefined
    • killSignal <string> | <integer> 当衍生的进程被杀死时将使用的信号值。默认值: 'SIGTERM'
    • maxBuffer <number> stdout 或 stderr 上允许的最大数据量(以字节为单位)。如果超出,子进程将被终止。请参阅 maxBuffer 与 Unicode 中的注意事项。默认值: 1024 * 1024
    • encoding <string> 用于所有 stdio 输入和输出的编码。默认值: 'buffer'
    • windowsHide <boolean> 隐藏通常在 Windows 系统上创建的子进程控制台窗口。默认值: false
    • shell <boolean> | <string> 如果为 true,则在 shell 中运行 command。在 Unix 上使用 '/bin/sh',在 Windows 上使用 process.env.ComSpec。可以指定一个不同的 shell 作为字符串。请参阅Shell 要求默认 Windows shell默认值: false (无 shell)。
  • 返回:<Buffer> | <string> 来自命令的 stdout。

child_process.execFileSync() 方法通常与 child_process.execFile() 相同,不同之处在于该方法在子进程完全关闭之前不会返回。当遇到超时并发送 killSignal 时,该方法在进程完全退出之前不会返回。

如果子进程拦截并处理了 SIGTERM 信号但没有退出,父进程仍将等待直到子进程退出。

如果进程超时或退出码非零,此方法将抛出一个 Error,其中将包含底层 child_process.spawnSync() 的完整结果。

如果启用了 shell 选项,切勿将未经处理的用户输入传递给此函数。任何包含 shell 元字符的输入都可能被用于触发任意命令执行。

const { execFileSync } = require('node:child_process');

try {
  const stdout = execFileSync('my-script.sh', ['my-arg'], {
    // Capture stdout and stderr from child process. Overrides the
    // default behavior of streaming child stderr to the parent stderr
    stdio: 'pipe',

    // Use utf8 encoding for stdio pipes
    encoding: 'utf8',
  });

  console.log(stdout);
} catch (err) {
  if (err.code) {
    // Spawning child process failed
    console.error(err.code);
  } else {
    // Child was spawned but exited with non-zero exit code
    // Error contains any stdout and stderr from the child
    const { stdout, stderr } = err;

    console.error({ stdout, stderr });
  }
}import { execFileSync } from 'node:child_process';

try {
  const stdout = execFileSync('my-script.sh', ['my-arg'], {
    // Capture stdout and stderr from child process. Overrides the
    // default behavior of streaming child stderr to the parent stderr
    stdio: 'pipe',

    // Use utf8 encoding for stdio pipes
    encoding: 'utf8',
  });

  console.log(stdout);
} catch (err) {
  if (err.code) {
    // Spawning child process failed
    console.error(err.code);
  } else {
    // Child was spawned but exited with non-zero exit code
    // Error contains any stdout and stderr from the child
    const { stdout, stderr } = err;

    console.error({ stdout, stderr });
  }
}

child_process.execSync(command[, options])#

  • command <string> 要运行的命令。
  • options <Object>
    • cwd <string> | <URL> 子进程的当前工作目录。
    • input <string> | <Buffer> | <TypedArray> | <DataView> 将作为 stdin 传递给衍生进程的值。如果 stdio[0] 设置为 'pipe',提供此值将覆盖 stdio[0]
    • stdio <string> | <Array> 子进程的 stdio 配置。参见 child_process.spawn()stdio。默认情况下,stderr 将输出到父进程的 stderr,除非指定了 stdio默认值: 'pipe'
    • env <Object> 环境变量键值对。默认值: process.env
    • shell <string> 用于执行命令的 shell。参见Shell 要求默认 Windows shell默认值: Unix 上是 '/bin/sh',Windows 上是 process.env.ComSpec
    • uid <number> 设置进程的用户标识。(参见 setuid(2))。
    • gid <number> 设置进程的组标识。(参见 setgid(2))。
    • timeout <number> 进程被允许运行的最长时间(以毫秒为单位)。默认值: undefined
    • killSignal <string> | <integer> 当衍生的进程被杀死时将使用的信号值。默认值: 'SIGTERM'
    • maxBuffer <number> stdout 或 stderr 上允许的最大数据量(以字节为单位)。如果超出,子进程将被终止,任何输出都将被截断。请参阅 maxBuffer 与 Unicode 中的注意事项。默认值: 1024 * 1024
    • encoding <string> 用于所有 stdio 输入和输出的编码。默认值: 'buffer'
    • windowsHide <boolean> 隐藏通常在 Windows 系统上创建的子进程控制台窗口。默认值: false
  • 返回:<Buffer> | <string> 来自命令的 stdout。

child_process.execSync() 方法通常与 child_process.exec() 相同,不同之处在于该方法在子进程完全关闭之前不会返回。当遇到超时并发送 killSignal 时,该方法在进程完全退出之前不会返回。如果子进程拦截并处理了 SIGTERM 信号但没有退出,父进程将等待直到子进程退出。

如果进程超时或退出码非零,此方法将抛出异常。该 Error 对象将包含来自 child_process.spawnSync() 的完整结果。

切勿将未经处理的用户输入传递给此函数。任何包含 shell 元字符的输入都可能被用于触发任意命令执行。

child_process.spawnSync(command[, args][, options])#

  • command <string> 要运行的命令。
  • args <string[]> 字符串参数列表。
  • options <Object>
    • cwd <string> | <URL> 子进程的当前工作目录。
    • input <string> | <Buffer> | <TypedArray> | <DataView> 将作为 stdin 传递给衍生进程的值。如果 stdio[0] 设置为 'pipe',提供此值将覆盖 stdio[0]
    • argv0 <string> 显式设置发送给子进程的 argv[0] 的值。如果未指定,将设置为 command
    • stdio <string> | <Array> 子进程的 stdio 配置。参见 child_process.spawn()stdio默认值: 'pipe'
    • env <Object> 环境变量键值对。默认值: process.env
    • uid <number> 设置进程的用户标识(参见 setuid(2))。
    • gid <number> 设置进程的组标识(参见 setgid(2))。
    • timeout <number> 进程被允许运行的最长时间(以毫秒为单位)。默认值: undefined
    • killSignal <string> | <integer> 当衍生的进程被杀死时将使用的信号值。默认值: 'SIGTERM'
    • maxBuffer <number> stdout 或 stderr 上允许的最大数据量(以字节为单位)。如果超出,子进程将被终止,任何输出都将被截断。请参阅 maxBuffer 与 Unicode 中的注意事项。默认值: 1024 * 1024
    • encoding <string> 用于所有 stdio 输入和输出的编码。默认值: 'buffer'
    • shell <boolean> | <string> 如果为 true,则在 shell 中运行 command。在 Unix 上使用 '/bin/sh',在 Windows 上使用 process.env.ComSpec。可以指定一个不同的 shell 作为字符串。请参阅Shell 要求默认 Windows shell默认值: false (无 shell)。
    • windowsVerbatimArguments <boolean> 在 Windows 上不对参数进行引用或转义。在 Unix 上被忽略。当指定 shell 并且是 CMD 时,此选项会自动设置为 true默认值: false
    • windowsHide <boolean> 隐藏通常在 Windows 系统上创建的子进程控制台窗口。默认值: false
  • 返回:<Object>
    • pid <number> 子进程的 Pid。
    • output <Array> 来自 stdio 输出的结果数组。
    • stdout <Buffer> | <string> output[1] 的内容。
    • stderr <Buffer> | <string> output[2] 的内容。
    • status <number> | <null> 子进程的退出码,如果子进程因信号终止则为 null
    • signal <string> | <null> 用于杀死子进程的信号,如果子进程不是因信号终止则为 null
    • error <Error> 如果子进程失败或超时,则为错误对象。

child_process.spawnSync() 方法通常与 child_process.spawn() 相同,不同之处在于该函数在子进程完全关闭之前不会返回。当遇到超时并发送 killSignal 时,该方法在进程完全退出之前不会返回。如果进程拦截并处理了 SIGTERM 信号但没有退出,父进程将等待直到子进程退出。

如果启用了 shell 选项,切勿将未经处理的用户输入传递给此函数。任何包含 shell 元字符的输入都可能被用于触发任意命令执行。

类:ChildProcess#

ChildProcess 的实例代表衍生的子进程。

ChildProcess 的实例不应直接创建。相反,应使用 child_process.spawn()child_process.exec()child_process.execFile()child_process.fork() 方法来创建 ChildProcess 的实例。

事件:'close'#

  • code <number> 如果子进程自行退出,则为退出码;如果子进程因信号终止,则为 null
  • signal <string> 子进程被终止时所用的信号;如果子进程不是因信号终止,则为 null

'close' 事件在一个进程结束后并且子进程的 stdio 流被关闭后发出。这与 'exit' 事件不同,因为多个进程可能共享相同的 stdio 流。'close' 事件总是在 'exit' 事件已经发出之后,或者在子进程衍生失败时在 'error' 事件之后发出。

如果进程已退出,code 是进程的最终退出码,否则为 null。如果进程因接收到信号而终止,signal 是信号的字符串名称,否则为 null。两者中总有一个是非 null

const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);

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

ls.on('close', (code) => {
  console.log(`child process close all stdio with code ${code}`);
});

ls.on('exit', (code) => {
  console.log(`child process exited with code ${code}`);
});import { spawn } from 'node:child_process';
const ls = spawn('ls', ['-lh', '/usr']);

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

ls.on('close', (code) => {
  console.log(`child process close all stdio with code ${code}`);
});

ls.on('exit', (code) => {
  console.log(`child process exited with code ${code}`);
});

事件:'disconnect'#

在父进程中调用 subprocess.disconnect() 方法或在子进程中调用 process.disconnect() 后,会发出 'disconnect' 事件。断开连接后,不再可能发送或接收消息,并且 subprocess.connected 属性为 false

事件:'error'#

'error' 事件在以下情况时发出:

  • 无法衍生该进程。
  • 无法杀死该进程。
  • 向子进程发送消息失败。
  • 子进程通过 signal 选项被中止。

发生错误后,'exit' 事件可能会也可能不会触发。当同时监听 'exit''error' 事件时,要防止意外地多次调用处理函数。

另请参见 subprocess.kill()subprocess.send()

事件:'exit'#

  • code <number> 如果子进程自行退出,则为退出码;如果子进程因信号终止,则为 null
  • signal <string> 子进程被终止时所用的信号;如果子进程不是因信号终止,则为 null

'exit' 事件在子进程结束后发出。如果进程已退出,code 是进程的最终退出码,否则为 null。如果进程因接收到信号而终止,signal 是信号的字符串名称,否则为 null。两者中总有一个是非 null

'exit' 事件触发时,子进程的 stdio 流可能仍然是打开的。

Node.js 为 SIGINTSIGTERM 建立了信号处理程序,因此 Node.js 进程不会因为接收到这些信号而立即终止。相反,Node.js 将执行一系列清理操作,然后重新引发被处理的信号。

参见 waitpid(2)

事件:'message'#

当子进程使用 process.send() 发送消息时,会触发 'message' 事件。

消息会经过序列化和解析。最终的消息可能与最初发送的不同。

如果在衍生子进程时将 serialization 选项设置为 'advanced',则 message 参数可以包含 JSON 无法表示的数据。更多详情请参见高级序列化

事件:'spawn'#

'spawn' 事件在子进程成功衍生后立即发出。如果子进程未能成功衍生,则不会发出 'spawn' 事件,而是会发出 'error' 事件。

如果发出,'spawn' 事件会先于所有其他事件,并且在通过 stdoutstderr 接收到任何数据之前发生。

无论在衍生的进程内部是否发生错误,'spawn' 事件都会触发。例如,如果 bash some-command 成功衍生,'spawn' 事件会触发,即使 bash 可能未能衍生 some-command。这个注意事项也适用于使用 { shell: true } 的情况。

subprocess.channel#

  • 类型:<Object> 代表到子进程的 IPC 通道的管道。

subprocess.channel 属性是对子进程 IPC 通道的引用。如果不存在 IPC 通道,则此属性为 undefined

subprocess.channel.ref()#

如果之前调用了 .unref(),此方法会使 IPC 通道保持父进程的事件循环运行。

subprocess.channel.unref()#

此方法使 IPC 通道不保持父进程的事件循环运行,并允许其在通道仍然打开时完成。

subprocess.connected#

  • 类型:<boolean> 在调用 subprocess.disconnect() 后设置为 false

subprocess.connected 属性指示是否仍然可以从子进程发送和接收消息。当 subprocess.connectedfalse 时,不再可能发送或接收消息。

subprocess.disconnect()#

关闭父子进程之间的 IPC 通道,允许子进程在没有其他连接使其保持活动状态时正常退出。调用此方法后,父进程和子进程(分别为)中的 subprocess.connectedprocess.connected 属性将设置为 false,并且将不再可能在进程之间传递消息。

当没有正在接收的消息时,将发出 'disconnect' 事件。这通常会在调用 subprocess.disconnect() 后立即触发。

当子进程是 Node.js 实例时(例如,使用 child_process.fork() 衍生),可以在子进程内部调用 process.disconnect() 方法来关闭 IPC 通道。

subprocess.exitCode#

subprocess.exitCode 属性表示子进程的退出码。如果子进程仍在运行,该字段将为 null

subprocess.kill([signal])#

subprocess.kill() 方法向子进程发送一个信号。如果没有提供参数,进程将被发送 'SIGTERM' 信号。有关可用信号的列表,请参见 signal(7)。如果 kill(2) 成功,此函数返回 true,否则返回 false

const { spawn } = require('node:child_process');
const grep = spawn('grep', ['ssh']);

grep.on('close', (code, signal) => {
  console.log(
    `child process terminated due to receipt of signal ${signal}`);
});

// Send SIGHUP to process.
grep.kill('SIGHUP');import { spawn } from 'node:child_process';
const grep = spawn('grep', ['ssh']);

grep.on('close', (code, signal) => {
  console.log(
    `child process terminated due to receipt of signal ${signal}`);
});

// Send SIGHUP to process.
grep.kill('SIGHUP');

如果信号无法传递,ChildProcess 对象可能会发出一个 'error' 事件。向一个已经退出的子进程发送信号不是错误,但可能会产生不可预见的后果。具体来说,如果进程标识符(PID)已被重新分配给另一个进程,信号将被传递给那个进程,这可能会产生意想不到的结果。

虽然该函数名为 kill,但传递给子进程的信号可能实际上不会终止该进程。

请参阅 kill(2) 以供参考。

在 Windows 上,由于不存在 POSIX 信号,signal 参数将被忽略,除了 'SIGKILL''SIGTERM''SIGINT''SIGQUIT',进程将总是被强制和突然地杀死(类似于 'SIGKILL')。更多详情请参见信号事件

在 Linux 上,当试图杀死父进程时,子进程的子进程不会被终止。当在 shell 中运行新进程或使用 ChildProcessshell 选项时,很可能会发生这种情况。

const { spawn } = require('node:child_process');

const subprocess = spawn(
  'sh',
  [
    '-c',
    `node -e "setInterval(() => {
      console.log(process.pid, 'is alive')
    }, 500);"`,
  ], {
    stdio: ['inherit', 'inherit', 'inherit'],
  },
);

setTimeout(() => {
  subprocess.kill(); // Does not terminate the Node.js process in the shell.
}, 2000);import { spawn } from 'node:child_process';

const subprocess = spawn(
  'sh',
  [
    '-c',
    `node -e "setInterval(() => {
      console.log(process.pid, 'is alive')
    }, 500);"`,
  ], {
    stdio: ['inherit', 'inherit', 'inherit'],
  },
);

setTimeout(() => {
  subprocess.kill(); // Does not terminate the Node.js process in the shell.
}, 2000);

subprocess[Symbol.dispose]()#

使用 'SIGTERM' 调用 subprocess.kill()

subprocess.killed#

  • 类型:<boolean> 在使用 subprocess.kill() 成功向子进程发送信号后设置为 true

subprocess.killed 属性指示子进程是否成功从 subprocess.kill() 接收到信号。killed 属性并不表示子进程已被终止。

subprocess.pid#

返回子进程的进程标识符(PID)。如果子进程因错误未能衍生,则该值为 undefined 并发出 error

const { spawn } = require('node:child_process');
const grep = spawn('grep', ['ssh']);

console.log(`Spawned child pid: ${grep.pid}`);
grep.stdin.end();import { spawn } from 'node:child_process';
const grep = spawn('grep', ['ssh']);

console.log(`Spawned child pid: ${grep.pid}`);
grep.stdin.end();

subprocess.ref()#

在调用 subprocess.unref() 后再调用 subprocess.ref(),将恢复被移除的子进程引用计数,强制父进程在自己退出前等待子进程退出。

const { spawn } = require('node:child_process');
const process = require('node:process');

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();
subprocess.ref();import { spawn } from 'node:child_process';
import process from 'node:process';

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();
subprocess.ref();

subprocess.send(message[, sendHandle[, options]][, callback])#

当父子进程之间建立了 IPC 通道时(即,当使用 child_process.fork() 时),可以使用 subprocess.send() 方法向子进程发送消息。当子进程是 Node.js 实例时,这些消息可以通过 'message' 事件接收。

消息会经过序列化和解析。最终的消息可能与最初发送的不同。

例如,在父脚本中:

const { fork } = require('node:child_process');
const forkedProcess = fork(`${__dirname}/sub.js`);

forkedProcess.on('message', (message) => {
  console.log('PARENT got message:', message);
});

// Causes the child to print: CHILD got message: { hello: 'world' }
forkedProcess.send({ hello: 'world' });import { fork } from 'node:child_process';
const forkedProcess = fork(`${import.meta.dirname}/sub.js`);

forkedProcess.on('message', (message) => {
  console.log('PARENT got message:', message);
});

// Causes the child to print: CHILD got message: { hello: 'world' }
forkedProcess.send({ hello: 'world' });

然后子脚本 'sub.js' 可能看起来像这样:

process.on('message', (message) => {
  console.log('CHILD got message:', message);
});

// Causes the parent to print: PARENT got message: { foo: 'bar', baz: null }
process.send({ foo: 'bar', baz: NaN }); 

子 Node.js 进程将有自己的 process.send() 方法,允许子进程向父进程发送回消息。

发送 {cmd: 'NODE_foo'} 消息时有一个特殊情况。cmd 属性中包含 NODE_ 前缀的消息是为 Node.js 核心内部使用而保留的,不会在子进程的 'message' 事件中发出。相反,此类消息使用 'internalMessage' 事件发出,并由 Node.js 内部消费。应用程序应避免使用此类消息或监听 'internalMessage' 事件,因为它可能会在没有通知的情况下更改。

可以传递给 subprocess.send() 的可选 sendHandle 参数用于将 TCP 服务器或套接字对象传递给子进程。子进程将收到的对象作为传递给在 'message' 事件上注册的回调函数的第二个参数。在套接字中接收和缓冲的任何数据都不会发送给子进程。在 Windows 上不支持发送 IPC 套接字。

可选的 callback 是一个在消息发送后、但在子进程可能收到它之前调用的函数。该函数以单个参数调用:成功时为 null,失败时为 Error 对象。

如果没有提供 callback 函数且消息无法发送,ChildProcess 对象将发出一个 'error' 事件。例如,当子进程已经退出时,可能会发生这种情况。

如果通道已关闭,或者未发送消息的积压超过了不宜再发送的阈值,subprocess.send() 将返回 false。否则,该方法返回 truecallback 函数可用于实现流量控制。

示例:发送一个 server 对象#

sendHandle 参数可以用于,例如,将 TCP 服务器对象的句柄传递给子进程,如下例所示:

const { fork } = require('node:child_process');
const { createServer } = require('node:net');

const subprocess = fork('subprocess.js');

// Open up the server object and send the handle.
const server = createServer();
server.on('connection', (socket) => {
  socket.end('handled by parent');
});
server.listen(1337, () => {
  subprocess.send('server', server);
});import { fork } from 'node:child_process';
import { createServer } from 'node:net';

const subprocess = fork('subprocess.js');

// Open up the server object and send the handle.
const server = createServer();
server.on('connection', (socket) => {
  socket.end('handled by parent');
});
server.listen(1337, () => {
  subprocess.send('server', server);
});

子进程将接收到服务器对象,如下所示:

process.on('message', (m, server) => {
  if (m === 'server') {
    server.on('connection', (socket) => {
      socket.end('handled by child');
    });
  }
}); 

一旦服务器在父子进程之间共享,一些连接可以由父进程处理,一些由子进程处理。

虽然上面的例子使用了 node:net 模块创建的服务器,但 node:dgram 模块的服务器使用完全相同的工作流程,除了监听 'message' 事件而不是 'connection',以及使用 server.bind() 而不是 server.listen()。然而,这只在 Unix 平台上受支持。

示例:发送一个 socket 对象#

类似地,sendHandler 参数可用于将套接字的句柄传递给子进程。下面的例子衍生了两个子进程,每个子进程处理“正常”或“特殊”优先级的连接:

const { fork } = require('node:child_process');
const { createServer } = require('node:net');

const normal = fork('subprocess.js', ['normal']);
const special = fork('subprocess.js', ['special']);

// Open up the server and send sockets to child. Use pauseOnConnect to prevent
// the sockets from being read before they are sent to the child process.
const server = createServer({ pauseOnConnect: true });
server.on('connection', (socket) => {

  // If this is special priority...
  if (socket.remoteAddress === '74.125.127.100') {
    special.send('socket', socket);
    return;
  }
  // This is normal priority.
  normal.send('socket', socket);
});
server.listen(1337);import { fork } from 'node:child_process';
import { createServer } from 'node:net';

const normal = fork('subprocess.js', ['normal']);
const special = fork('subprocess.js', ['special']);

// Open up the server and send sockets to child. Use pauseOnConnect to prevent
// the sockets from being read before they are sent to the child process.
const server = createServer({ pauseOnConnect: true });
server.on('connection', (socket) => {

  // If this is special priority...
  if (socket.remoteAddress === '74.125.127.100') {
    special.send('socket', socket);
    return;
  }
  // This is normal priority.
  normal.send('socket', socket);
});
server.listen(1337);

subprocess.js 将收到套接字句柄作为传递给事件回调函数的第二个参数:

process.on('message', (m, socket) => {
  if (m === 'socket') {
    if (socket) {
      // Check that the client socket exists.
      // It is possible for the socket to be closed between the time it is
      // sent and the time it is received in the child process.
      socket.end(`Request handled with ${process.argv[2]} priority`);
    }
  }
}); 

不要在已传递给子进程的套接字上使用 .maxConnections。父进程无法跟踪套接字何时被销毁。

子进程中的任何 'message' 处理程序都应验证 socket 是否存在,因为在将连接发送给子进程期间,连接可能已经关闭。

subprocess.signalCode#

subprocess.signalCode 属性表示子进程收到的信号(如果有),否则为 null

subprocess.spawnargs#

subprocess.spawnargs 属性表示子进程启动时使用的完整命令行参数列表。

subprocess.spawnfile#

subprocess.spawnfile 属性表示启动的子进程的可执行文件名。

对于 child_process.fork(),其值将等于 process.execPath。对于 child_process.spawn(),其值将是可执行文件的名称。对于 child_process.exec(),其值将是启动子进程的 shell 的名称。

subprocess.stderr#

一个代表子进程 stderrReadable Stream

如果子进程衍生时 stdio[2] 设置为除 'pipe' 之外的任何值,则此值为 null

subprocess.stderrsubprocess.stdio[2] 的别名。两个属性都将引用相同的值。

如果子进程无法成功衍生,subprocess.stderr 属性可能为 nullundefined

subprocess.stdin#

一个代表子进程 stdinWritable Stream

如果子进程等待读取其所有输入,则子进程将不会继续,直到此流通过 end() 关闭。

如果子进程衍生时 stdio[0] 设置为除 'pipe' 之外的任何值,则此值为 null

subprocess.stdinsubprocess.stdio[0] 的别名。两个属性都将引用相同的值。

如果子进程无法成功衍生,subprocess.stdin 属性可能为 nullundefined

subprocess.stdio#

一个到子进程的稀疏管道数组,对应于传递给 child_process.spawn()stdio 选项中已设置为 'pipe' 值的位置。subprocess.stdio[0]subprocess.stdio[1]subprocess.stdio[2] 也分别作为 subprocess.stdinsubprocess.stdoutsubprocess.stderr 可用。

在以下示例中,只有子进程的 fd 1 (stdout) 被配置为管道,因此只有父进程的 subprocess.stdio[1] 是一个流,数组中的所有其他值均为 null

const assert = require('node:assert');
const fs = require('node:fs');
const child_process = require('node:child_process');

const subprocess = child_process.spawn('ls', {
  stdio: [
    0, // Use parent's stdin for child.
    'pipe', // Pipe child's stdout to parent.
    fs.openSync('err.out', 'w'), // Direct child's stderr to a file.
  ],
});

assert.strictEqual(subprocess.stdio[0], null);
assert.strictEqual(subprocess.stdio[0], subprocess.stdin);

assert(subprocess.stdout);
assert.strictEqual(subprocess.stdio[1], subprocess.stdout);

assert.strictEqual(subprocess.stdio[2], null);
assert.strictEqual(subprocess.stdio[2], subprocess.stderr);import assert from 'node:assert';
import fs from 'node:fs';
import child_process from 'node:child_process';

const subprocess = child_process.spawn('ls', {
  stdio: [
    0, // Use parent's stdin for child.
    'pipe', // Pipe child's stdout to parent.
    fs.openSync('err.out', 'w'), // Direct child's stderr to a file.
  ],
});

assert.strictEqual(subprocess.stdio[0], null);
assert.strictEqual(subprocess.stdio[0], subprocess.stdin);

assert(subprocess.stdout);
assert.strictEqual(subprocess.stdio[1], subprocess.stdout);

assert.strictEqual(subprocess.stdio[2], null);
assert.strictEqual(subprocess.stdio[2], subprocess.stderr);

如果子进程无法成功衍生,subprocess.stdio 属性可能为 undefined

subprocess.stdout#

一个代表子进程 stdoutReadable Stream

如果子进程衍生时 stdio[1] 设置为除 'pipe' 之外的任何值,则此值为 null

subprocess.stdoutsubprocess.stdio[1] 的别名。两个属性都将引用相同的值。

const { spawn } = require('node:child_process');

const subprocess = spawn('ls');

subprocess.stdout.on('data', (data) => {
  console.log(`Received chunk ${data}`);
});import { spawn } from 'node:child_process';

const subprocess = spawn('ls');

subprocess.stdout.on('data', (data) => {
  console.log(`Received chunk ${data}`);
});

如果子进程无法成功衍生,subprocess.stdout 属性可能为 nullundefined

subprocess.unref()#

默认情况下,父进程将等待分离的子进程退出。要防止父进程等待给定的 subprocess 退出,请使用 subprocess.unref() 方法。这样做将导致父进程的事件循环不将子进程包含在其引用计数中,允许父进程独立于子进程退出,除非在子进程和父进程之间建立了 IPC 通道。

const { spawn } = require('node:child_process');
const process = require('node:process');

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();import { spawn } from 'node:child_process';
import process from 'node:process';

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();

maxBuffer 与 Unicode#

maxBuffer 选项指定 stdoutstderr 上允许的最大字节数。如果超过此值,则子进程将被终止。这会影响包含多字节字符编码(如 UTF-8 或 UTF-16)的输出。例如,console.log('中文测试') 将向 stdout 发送 13 个 UTF-8 编码的字节,尽管只有 4 个字符。

Shell 要求#

Shell 应该理解 -c 开关。如果 shell 是 'cmd.exe',它应该理解 /d /s /c 开关,并且命令行解析应该兼容。

默认的 Windows shell#

尽管微软规定 %COMSPEC% 必须在根环境中包含到 'cmd.exe' 的路径,但子进程并不总是受此要求的约束。因此,在可以衍生 shell 的 child_process 函数中,如果 process.env.ComSpec 不可用,则使用 'cmd.exe' 作为后备。

高级序列化#

子进程支持一种用于 IPC 的序列化机制,该机制基于 node:v8 模块的序列化 API,并基于 HTML 结构化克隆算法。这通常功能更强大,并支持更多内置的 JavaScript 对象类型,如 BigIntMapSetArrayBufferTypedArrayBufferErrorRegExp 等。

然而,此格式并非 JSON 的完全超集,例如,在这些内置类型对象上设置的属性不会在序列化步骤中传递。此外,根据传递数据的结构,其性能可能与 JSON 不等。因此,此功能需要在调用 child_process.spawn()child_process.fork() 时,通过将 serialization 选项设置为 'advanced' 来选择性启用。