Node.js v8.1.3 文档


child_process (子进程)#

查看英文版 / 参与翻译

稳定性: 2 - 稳定的

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

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

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

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

ls.on('close', (code) => {
  console.log(`子进程退出码:${code}`);
});

默认情况下,在 Node.js 的父进程与衍生的子进程之间会建立 stdinstdoutstderr 的管道。 数据能以非阻塞的方式在管道中流通。 注意,有些程序会在内部使用行缓冲 I/O。 虽然这并不影响 Node.js,但这意味着发送到子过程的数据可能无法被立即使用。

child_process.spawn() 方法会异步地衍生子进程,且不会阻塞 Node.js 事件循环。 child_process.spawnSync() 方法则以同步的方式提供同样的功能,但会阻塞事件循环,直到衍生的子进程退出或终止。

为了方便起见,child_process 模块提供了一些同步和异步的替代方法用于 child_process.spawn()child_process.spawnSync()。 注意,每个替代方法都是在 child_process.spawn()child_process.spawnSync() 的基础上实现的。

  • child_process.exec(): 衍生一个 shell 并在 shell 上运行命令,当完成时会传入 stdoutstderr 到回调函数。
  • child_process.execFile(): 类似 child_process.exec(),但直接衍生命令,且无需先衍生一个 shell。
  • child_process.fork(): 衍生一个新的 Node.js 进程,并通过建立一个 IPC 通讯通道来调用一个指定的模块,该通道允许父进程与子进程之间相互发送信息。
  • child_process.execSync(): child_process.exec() 的同步方法,会阻塞 Node.js 事件循环。
  • child_process.execFileSync(): child_process.execFile() 的同步方法,会阻塞 Node.js 事件循环。

对于某些用例,如自动化的 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() 所做的工作)。 在任何情况下,如果脚本文件名包含了空格,则需要用加上引号。

// 仅限 Windows 系统
const { spawn } = require('child_process');
const bat = spawn('cmd.exe', ['/c', 'my.bat']);

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

bat.stderr.on('data', (data) => {
  console.log(data.toString());
});

bat.on('exit', (code) => {
  console.log(`子进程退出码:${code}`);
});
// 或
const { exec } = require('child_process');
exec('my.bat', (err, stdout, stderr) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(stdout);
});

// 文件名带有空格的脚本:
const bat = spawn('"my script.cmd"', ['a', 'b'], { shell: true });
// 或:
exec('"my script.cmd" a b', (err, stdout, stderr) => {
  // ...
});

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

查看英文版 / 参与翻译

衍生一个 shell,然后在 shell 中执行 command,且缓冲任何产生的输出。 The command string passed to the exec function is processed directly by the shell and special characters (vary based on shell) need to be dealt with accordingly:

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

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

注意:不要把未经检查的用户输入传入到该函数。 任何包括 shell 元字符的输入都可被用于触发任何命令的执行。

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

如果提供了一个 callback 函数,则它被调用时会带上参数 (error, stdout, stderr)。 当成功时,error 会是 null。 当失败时,error 会是一个 Error 实例。 error.code 属性会是子进程的退出码,error.signal 会被设为终止进程的信号。 除 0 以外的任何退出码都被认为是一个错误。

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

options 参数可以作为第二个参数传入,用于自定义如何衍生进程。 默认的选项是:

const defaults = {
  encoding: 'utf8',
  timeout: 0,
  maxBuffer: 200 * 1024,
  killSignal: 'SIGTERM',
  cwd: null,
  env: null
};

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

注意:不像 POSIX 系统调用中的 exec(3)child_process.exec() 不会替换现有的进程,且使用一个 shell 来执行命令。

If this method is invoked as its util.promisify()ed version, it returns a Promise for an object with stdout and stderr properties. In case of an error, a rejected promise is returned, with the same error object given in the callback, but with an additional two properties stdout and stderr.

For example:

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

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

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

查看英文版 / 参与翻译

child_process.execFile() 函数类似 child_process.exec(),除了不衍生一个 shell。 而是,指定的可执行的 file 被直接衍生为一个新进程,这使得它比 child_process.exec() 更高效。

它支持和 child_process.exec() 一样的选项。 由于没有衍生 shell,因此不支持像 I/O 重定向和文件查找这样的行为。

const { execFile } = require('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() 版本, 它会返回一个拥有 stdoutstderr 属性的 Promise 对象. 在发生错误的情况下, 返回一个被拒绝的 promise, 拥有与回调 函数一样的 error 对象, 但是附加了 stdoutstderr 这两个属性.

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

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

查看英文版 / 参与翻译

  • modulePath <string> 要在子进程中运行的模块
  • args <Array> 字符串参数列表
  • options <Object>
    • cwd <string> 子进程的当前工作目录
    • env <Object> 环境变量键值对
    • execPath <string> 用来创建子进程的执行路径
    • execArgv <Array> 要传给执行路径的字符串参数列表 (默认: process.execArgv
    • 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)
    • gid <number> 设置该进程的组标识。(详见 setgid(2)
  • 返回: <ChildProcess>

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

衍生的 Node.js 子进程与两者之间建立的 IPC 通信信道的异常是独立于父进程的。 每个进程都有自己的内存,使用自己的 V8 实例。 由于需要额外的资源分配,因此不推荐衍生大量的 Node.js 进程。

默认情况下,child_process.fork() 会使用父进程中的 process.execPath 衍生新的 Node.js 实例。 options 对象中的 execPath 属性可以替换要使用的执行路径。

使用自定义的 execPath 启动的 Node.js 进程,会使用子进程的环境变量 NODE_CHANNEL_FD 中指定的文件描述符(fd)与父进程通信。 fd 上的输入和输出期望被分割成一行一行的 JSON 对象。

注意,不像 POSIX 系统回调中的 fork(2)child_process.fork() 不会克隆当前进程。

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

查看英文版 / 参与翻译

  • command <string> 要运行的命令
  • args <Array> 字符串参数列表
  • options <Object>
    • cwd <string> 子进程的当前工作目录
    • env <Object> 环境变量键值对
    • argv0 <string> 显式地设置要发给子进程的 argv[0] 的值。 如果未指定,则设为 command
    • stdio <Array> | <string> 子进程的 stdio 配置。 (详见 options.stdio
    • detached <boolean> 准备将子进程独立于父进程运行。 具体行为取决于平台。(详见 options.detached
    • uid <number> 设置该进程的用户标识。(详见 setuid(2)
    • gid <number> 设置该进程的组标识。(详见 setgid(2)
    • shell <boolean> | <string> 如果为 true,则在一个 shell 中运行 command。 在 UNIX 上使用 '/bin/sh',在 Windows 上使用 'cmd.exe'。 一个不同的 shell 可以被指定为字符串。 该 shell 应该理解 UNIX 上的 -c 开关、或 Windows 的 /d /s /c。 默认为 false(没有 shell)。
  • 返回: <ChildProcess>

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

注意:不要把未经检查的用户输入传入到该函数。 任何包括 shell 元字符的输入都可被用于触发任何命令的执行。

第三个参数可以用来指定额外的选项,默认如下:

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

使用 cwd 来指定衍生的进程的工作目录。 如果没有给出,则默认继承当前的工作目录。

使用 env 来指定环境变量,这会在新进程中可见,默认为 process.env

例子,运行 ls -lh /usr,捕获 stdoutstderr、以及退出码:

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

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

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

ls.on('close', (code) => {
  console.log(`子进程退出码:${code}`);
});

例子,一种执行 'ps ax | grep ssh' 的方法:

const { spawn } = require('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.log(`ps stderr: ${data}`);
});

ps.on('close', (code) => {
  if (code !== 0) {
    console.log(`ps 进程退出码:${code}`);
  }
  grep.stdin.end();
});

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

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

grep.on('close', (code) => {
  if (code !== 0) {
    console.log(`grep 进程退出码:${code}`);
  }
});

例子,检测失败的执行:

const { spawn } = require('child_process');
const child = spawn('bad_command');

child.on('error', (err) => {
  console.log('启动子进程失败。');
});

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

注意,Node.js 一般会在启动时用 process.execPath 重写 argv[0],所以 Node.js 子进程中的 process.argv[0] 不会匹配从父进程传给 spawnargv0 参数,可以使用 process.argv0 属性获取它。

options.detached#

查看英文版 / 参与翻译

在 Windows 上,设置 options.detachedtrue 可以使子进程在父进程退出后继续运行。 子进程有自己的控制台窗口。 一旦启用一个子进程,它将不能被禁用。

在非 Windows 平台上,如果将 options.detached 设为 true,则子进程会成为新的进程组和会话的领导者。 注意,子进程在父进程退出后可以继续运行,不管它们是否被分离。 详见 setsid(2)

默认情况下,父进程会等待被分离的子进程退出。 为了防止父进程等待给定的 child,可以使用 child.unref() 方法。 这样做会导致父进程的事件循环不包含子进程的引用计数,使得父进程独立于子进程退出,除非子进程和父进程之间建立了一个 IPC 信道。

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

例子,一个长期运行的进程,为了忽视父进程的终止,通过分离且忽视其父进程的 stdio 文件描述符来实现:

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

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

child.unref();

也可以将子进程的输出重定向到文件:

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

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

child.unref();

options.stdio#

查看英文版 / 参与翻译

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

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

  • 'pipe' - 等同于 ['pipe', 'pipe', 'pipe'] (默认)
  • 'ignore' - 等同于 ['ignore', 'ignore', 'ignore']
  • 'inherit' - 等同于 [process.stdin, process.stdout, process.stderr][0,1,2]

另外,option.stdio 的值是一个每个索引都对应一个子进程 fd 的数组。 fd 的 0、1 和 2 分别对应 stdin、stdout 和 stderr。 额外的 fd 可以被指定来创建父进程和子进程之间的额外管道。 该值是以下之一:

  1. 'pipe' - 创建一个子进程和父进程之间的管道。 在管道的父端以 child.stdio[fd] 的形式作为 child_process 对象的一个属性暴露给父进程。 为 fd 创建的管道 0 - 2 也可分别作为 child.stdinchild.stdoutchild.stderr
  2. 'ipc' - 创建一个用于父进程和子进程之间传递消息或文件描述符的 IPC 通道符。 一个 ChildProcess 最多只能有一个 IPC stdio 文件描述符。 设置该选项可启用 child.send() 方法。 如果子进程把 JSON 消息写入到该文件描述符,则 child.on('message') 事件句柄会被父进程触发。 如果子进程是一个 Node.js 进程,则一个已存在的 IPC 通道会在子进程中启用 process.send()process.disconnect()process.on('disconnect')process.on('message')
  3. 'ignore' - 指示 Node.js 在子进程中忽略 fd。 由于 Node.js 总是会为它衍生的进程打开 fd 0 - 2,所以设置 fd 为 'ignore' 可以使 Node.js 打开 /dev/null 并将它附加到子进程的 fd 上。
  4. <Stream> 对象 - 共享一个指向子进程的 tty、文件、socket 或管道的可读或可写流。 流的底层文件描述符在子进程中是重复对应该 stdio 数组的索引的 fd。 注意,该流必须有一个底层描述符(文件流直到 'open' 事件发生才需要)。
  5. 正整数 - 整数值被解析为一个正在父进程中打开的文件描述符。 它和子进程共享,类似于 <Stream> 是如何被共享的。
  6. null, undefined - 使用默认值。 对于 stdio fd 0、1 和 2(换言之,stdin、stdout 和 stderr)而言是创建了一个管道。 对于 fd 3 及以上而言,默认值为 'ignore'

例子:

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

// 子进程使用父进程的 stdios
spawn('prg', [], { stdio: 'inherit' });

// 衍生的子进程只共享 stderr
spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] });

// 打开一个额外的 fd=4,用于与程序交互
spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });

当在父进程和子进程之间建立了一个 IPC 通道,且子进程是一个 Node.js 进程,则子进程会带着未引用的 IPC 通道(使用 unref())启动,直到子进程为 process.on('disconnect') 事件或 process.on('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 <Array> 字符串参数列表
  • options <Object>
    • cwd <string> 子进程的当前工作目录
    • input <string> | <Buffer> | <Uint8Array> 要作为 stdin 传给衍生进程的值
      • 提供该值会覆盖 stdio[0]
    • stdio <string> | <Array> 子进程的 stdio 配置。(默认: 'pipe'
      • stderr 默认会输出到父进程中的 stderr,除非指定了 stdio
    • env <Object> 环境变量键值对
    • uid <number> 设置该进程的用户标识。(详见 setuid(2)
    • gid <number> 设置该进程的组标识。(详见 setgid(2)
    • timeout <number> 进程允许运行的最大时间数,以毫秒为单位。(默认: undefined
    • killSignal <string> | <integer> 当衍生进程将被杀死时要使用的信号值。(默认: 'SIGTERM'
    • maxBuffer <number> stdout 或 stderr 允许的最大字节数。 默认为 200*1024。 如果超过限制,则子进程会被终止。 See caveat at maxBuffer and Unicode.
    • encoding <string> 用于所有 stdio 输入和输出的编码。(默认: 'buffer'
  • 返回: <Buffer> | <string> 该命令的 stdout

child_process.execFileSync() 方法与 child_process.execFile() 基本相同,除了该方法直到子进程完全关闭后才返回。 当遇到超时且发送了 killSignal 时,则该方法直到进程完全退出后才返回结果。

注意,如果子进程拦截并处理了 SIGTERM 信号且没有退出,则父进程会一直等待直到子进程退出。

如果进程超时,或有一个非零的退出码,则该方法会抛出错误。 Error 对象会包含从 child_process.spawnSync() 返回的整个结果。

child_process.execSync(command[, options])#

查看英文版 / 参与翻译

  • command <string> 要运行的命令
  • options <Object>
    • cwd <string> 子进程的当前工作目录
    • input <string> | <Buffer> | <Uint8Array>} 要作为 stdin 传给衍生进程的值
      • 提供该值会覆盖 stdio[0]
    • stdio <string> | <Array> 子进程的 stdio 配置。(默认: 'pipe'
      • stderr 默认会输出到父进程中的 stderr,除非指定了 stdio
    • env <Object> 环境变量键值对
    • shell <string> 用于执行命令的 shell (默认:在 UNIX 上为 '/bin/sh',在 Windows 上为 'cmd.exe'。 该 shell 应该能够理解 UNIX 的 -c 开关或 Windows 的 /d /s /c 开关。 在 Windows 中,命令行的解析应与 cmd.exe 兼容。)
    • uid <number> 设置该进程的用户标识。(详见 setuid(2)
    • gid <number> 设置该进程的组标识。(详见 setgid(2)
    • timeout <number> 进程允许运行的最大时间数,以毫秒为单位。(默认: undefined
    • killSignal <string> | <integer> 当衍生进程将被杀死时要使用的信号值。(默认: 'SIGTERM'
    • maxBuffer <number> stdout 或 stderr 允许的最大字节数。 默认为 200*1024。 如果超过限制,则子进程会被终止。 See caveat at maxBuffer and Unicode.
    • encoding <string> 用于所有 stdio 输入和输出的编码。(默认: 'buffer'
  • 返回: <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 <Array> 字符串参数列表
  • options <Object>
    • cwd <string> 子进程的当前工作目录
    • input <string> | <Buffer> | <Uint8Array> 要作为 stdin 传给衍生进程的值
      • 提供该值会覆盖 stdio[0]
    • stdio <string> | <Array> 子进程的 stdio 配置
    • env <Object> 环境变量键值对
    • uid <number> 设置该进程的用户标识。(详见 setuid(2)
    • gid <number> 设置该进程的组标识。(详见 setgid(2)
    • timeout <number> 进程允许运行的最大时间数,以毫秒为单位。(默认: undefined
    • killSignal <string> | <integer> 当衍生进程将被杀死时要使用的信号值。(默认: 'SIGTERM'
    • maxBuffer <number> stdout 或 stderr 允许的最大字节数。 默认为 200*1024。 如果超过限制,则子进程会被终止。 See caveat at maxBuffer and Unicode.
    • encoding <string> 用于所有 stdio 输入和输出的编码。(默认: 'buffer'
    • shell <boolean> | <string> 如果为 true,则在一个 shell 中运行 command。 在 UNIX 上使用 '/bin/sh',在 Windows 上使用 'cmd.exe'。 一个不同的 shell 可以被指定为字符串。 该 shell 应该理解 UNIX 上的 -c 开关、或 Windows 的 /d /s /c。 默认为 false(没有 shell)。
  • 返回: <Object>

child_process.spawnSync() 方法与 child_process.spawn() 基本相同,除了该方法直到子进程完全关闭后才返回。 当遇到超时且发送了 killSignal 时,则该方法直到进程完全退出后才返回结果。 注意,如果子进程拦截并处理了 SIGTERM 信号且没有退出,则父进程会一直等待直到子进程退出。

注意:不要把未经检查的用户输入传入到该函数。 任何包括 shell 元字符的输入都可被用于触发任何命令的执行。

ChildProcess 类#

查看英文版 / 参与翻译

ChildProcess 类的实例是 EventEmitter,代表衍生的子进程。

ChildProcess 的实例不被直接创建。 而是,使用 child_process.spawn()child_process.exec()child_process.execFile()child_process.fork() 方法创建 ChildProcess 实例。

'close' 事件#

查看英文版 / 参与翻译

  • code <number> 如果子进程退出自身,则该值是退出码。
  • signal <string> 子进程被终止时的信号。

当子进程的 stdio 流被关闭时会触发 'close' 事件。 这与 'exit' 事件不同,因为多个进程可能共享同一 stdio 流。

'disconnect' 事件#

查看英文版 / 参与翻译

在父进程中调用 child.disconnect() 或在子进程中调用 process.disconnect() 后会触发 'disconnect' 事件。 断开后就不能再发送或接收信息,且 child.connected 属性会被设为 false

'error' 事件#

查看英文版 / 参与翻译

每当出现以下情况时触发 'error' 事件:

  1. 进程无法被衍生;

  2. 进程无法被杀死;

  3. 向子进程发送信息失败。

注意,在错误发生后,'exit' 事件可能会也可能不会触发。 当同时监听了 'exit''error' 事件,谨防处理函数被多次调用。

详见 child.kill()child.send()

'exit' 事件#

查看英文版 / 参与翻译

  • code <number> 如果子进程退出自身,则该值是退出码。
  • signal <string> 子进程被终止时的信号。

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

注意,当 'exit' 事件被触发时,子进程的 stdio 流可能依然是打开的。

另外,还要注意,Node.js 建立了 SIGINTSIGTERM 的信号处理程序,且 Node.js 进程收到这些信号也不会立即终止。 相反,Node.js 会执行一系列的清理操作后重新引发处理信号。

详见 waitpid(2)

'message' 事件#

查看英文版 / 参与翻译

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

child.channel#

查看英文版 / 参与翻译

  • <Object> A pipe representing the IPC channel to the child process.

The child.channel property is a reference to the child's IPC channel. If no IPC channel currently exists, this property is undefined.

child.connected#

查看英文版 / 参与翻译

  • <boolean> 调用 child.disconnect() 后会被设为 false

child.connected 属性表明是否仍可以从一个子进程发送和接收消息。 当 child.connectedfalse 时,则不能再发送或接收的消息。

child.disconnect()#

查看英文版 / 参与翻译

关闭父进程与子进程之间的 IPC 通道,一旦没有其他的连接使其保持活跃,则允许子进程正常退出。 调用该方法后,父进程和子进程上各自的 child.connectedprocess.connected 属性都会被设为 false,且进程之间不能再传递消息。

当正在接收的进程中没有消息时,就会触发 'disconnect' 事件。 这经常在调用 child.disconnect() 后立即被触发。

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

child.kill([signal])#

查看英文版 / 参与翻译

child.kill() 方法向子进程发送一个信号。 如果没有给定参数,则进程会发送 'SIGTERM' 信号。 查看 signal(7) 了解可用的信号列表。

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

grep.on('close', (code, signal) => {
  console.log(`子进程收到信号 ${signal} 而终止`);
});

// 发送 SIGHUP 到进程
grep.kill('SIGHUP');

如果信号没有被送达,ChildProcess 对象可能会触发一个 'error' 事件。 向一个已经退出的子进程发送信号不是一个错误,但可能有无法预知的后果。 特别是,如果进程的 PID 已经重新分配给其他进程,则信号会被发送到该进程,从而可能有意想不到的结果。

注意,当函数被调用 kill 时,已发送到子进程的信号可能没有实际终止该进程。

详见 kill(2)

注意:在 Linux 上,当试图杀死父进程时,子进程的子进程不会被终止。 这有可能发生在当在一个 shell 中运行一个新进程时,或使用 ChildProcess 中的 shell 选项时,例如:

'use strict';
const { spawn } = require('child_process');

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

setTimeout(() => {
  child.kill(); // 不会终止 shell 中的 node 进程
}, 2000);

child.pid#

查看英文版 / 参与翻译

返回子进程的进程标识(PID)。

例子:

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

console.log(`衍生的子进程的 pid:${grep.pid}`);
grep.stdin.end();

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

查看英文版 / 参与翻译

当父进程和子进程之间建立了一个 IPC 通道时(例如,使用 child_process.fork()),child.send() 方法可用于发送消息到子进程。 当子进程是一个 Node.js 实例时,消息可以通过 process.on('message') 事件接收。

例子,父进程脚本如下:

const cp = require('child_process');
const n = cp.fork(`${__dirname}/sub.js`);

n.on('message', (m) => {
  console.log('父进程收到消息:', m);
});

n.send({ hello: 'world' });

然后是子进程脚本,'sub.js' 可能看上去像这样:

process.on('message', (m) => {
  console.log('子进程收到消息:', m);
});

process.send({ foo: 'bar' });

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

当发送一个 {cmd: 'NODE_foo'} 消息时,是一个特例。 所有在 cmd 属性里包含一个 NODE_ 前缀的都会被认为是预留给 Node.js 核心代码内部使用的,且不会触发子进程的 process.on('message') 事件。 而是,这种消息可使用 process.on('internalMessage') 事件触发,且被 Node.js 内部消费。 应用程序应避免使用这种消息或监听 'internalMessage' 事件。

可选的 sendHandle 参数可能被传给 child.send(),它用于传入一个 TCP 服务器或 socket 对象给子进程。 子进程会接收对象作为第二个参数,并传给注册在 process.on('message') 事件上的回调函数。 socket 上接收或缓冲的任何数据不会被发送给子进程。

options 参数,如果存在的话,是一个用于处理发送数据参数对象。options 支持以下属性:

  • keepOpen - 一个 Boolean 值,当传入 net.Socket 实例时可用。 当为 true 时,socket 在发送进程中保持打开。 默认为 false

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

如果没有提供 callback 函数,且消息没被发送,则一个 'error' 事件将被 ChildProcess 对象触发。 这是有可能发生的,例如当子进程已经退出时。

如果通道已关闭,或当未发送的消息的积压超过阈值使其无法发送更多时,child.send() 会返回 false。 除此以外,该方法返回 truecallback 函数可用于实现流量控制。

例子:发送一个 server 对象#

查看英文版 / 参与翻译

sendHandle 参数可用于将一个 TCP server 对象句柄传给子进程,如下所示:

const child = require('child_process').fork('child.js');

// 开启 server 对象,并发送该句柄。
const server = require('net').createServer();
server.on('connection', (socket) => {
  socket.end('被父进程处理');
});
server.listen(1337, () => {
  child.send('server', server);
});

子进程接收 server 对象如下:

process.on('message', (m, server) => {
  if (m === 'server') {
    server.on('connection', (socket) => {
      socket.end('被子进程处理');
    });
  }
});

当服务器在父进程和子进程之间是共享的,则一些连接可被父进程处理,另一些可被子进程处理。

上面的例子使用了一个 net 模块创建的服务器,而 dgram 模块的服务器使用完全相同的工作流程,但它监听一个 'message' 事件而不是 'connection' 事件,且使用 server.bind 而不是 server.listen()。 目前仅 UNIX 平台支持这一点。

例子:发送一个 socket 对象#

查看英文版 / 参与翻译

同样,sendHandle 参数可用于将一个 socket 句柄传给子进程。 以下例子衍生了两个子进程,分别用于处理 "normal" 连接或优先处理 "special" 连接:

const normal = require('child_process').fork('child.js', ['normal']);
const special = require('child_process').fork('child.js', ['special']);

// 开启 server,并发送 socket 给子进程。
// 使用 `pauseOnConnect` 防止 socket 在被发送到子进程之前被读取。
const server = require('net').createServer({ pauseOnConnect: true });
server.on('connection', (socket) => {

  // 特殊优先级
  if (socket.remoteAddress === '74.125.127.100') {
    special.send('socket', socket);
    return;
  }
  // 普通优先级
  normal.send('socket', socket);
});
server.listen(1337);

child.js 会接收到一个 socket 句柄,并作为第二个参数传给事件回调函数:

process.on('message', (m, socket) => {
  if (m === 'socket') {
    socket.end(`请求被 ${process.argv[2]} 优先级处理`);
  }
});
process.on('message', (m, socket) => {
  if (m === 'socket') {
    if (socket) {
      // 检查客户端 socket 是否存在。
      // socket 在被发送与被子进程接收这段时间内可被关闭。
      socket.end(`请求被 ${process.argv[2]} 优先级处理`);
    }
  }
});

一旦一个 socket 已被传给了子进程,则父进程不再能够跟踪 socket 何时被销毁。 为了表明这个,.connections 属性会变成 null。 当发生这种情况时,建议不要使用 .maxConnections

It is also recommended that any 'message' handlers in the child process verify that socket exists, as the connection may have been closed during the time it takes to send the connection to the child.

注意,该函数内部使用 JSON.stringify() 序列化 message

child.stderr#

查看英文版 / 参与翻译

一个代表子进程的 stderr 的可读流。

如果子进程被衍生时 stdio[2] 被设为任何不是 'pipe' 的值,则这会是 null

child.stderrchild.stdio[2] 的一个别名。 这两个属性指向相同的值。

child.stdin#

查看英文版 / 参与翻译

一个代表子进程的 stdin 的可写流。

注意,如果一个子进程正在等待读取所有的输入,则子进程不会继续直到流已通过 end() 关闭。

如果子进程被衍生时 stdio[0] 被设为任何不是 'pipe' 的值,则这会是 null

child.stdinchild.stdio[0] 的一个别名。 这两个属性指向相同的值。

child.stdio#

查看英文版 / 参与翻译

一个到子进程的管道的稀疏数组,对应着传给 child_process.spawn() 的选项中值被设为 'pipe'stdio。 注意,child.stdio[0]child.stdio[1]child.stdio[2] 分别可用作 child.stdinchild.stdoutchild.stderr

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

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

const child = child_process.spawn('ls', {
  stdio: [
    0, // 使用父进程的 stdin 用于子进程
    'pipe', // 把子进程的 stdout 通过管道传到父进程 
    fs.openSync('err.out', 'w') // 把子进程的 stderr 指向一个文件
  ]
});

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

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

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

child.stdout#

查看英文版 / 参与翻译

一个代表子进程的 stdout 的可读流。

如果子进程被衍生时 stdio[1] 被设为任何不是 'pipe' 的值,则这会是 null

child.stdoutchild.stdio[1] 的一个别名。 这两个属性指向相同的值。

maxBuffer 与 Unicode#

查看英文版 / 参与翻译

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