Node.js v10.14.2 文档


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 的管道。

child_process.spawn() 用于异步地衍生子进程,且不会阻塞 Node.js 事件循环。 child_process.spawnSync() 则同步地衍生子进程,但会阻塞事件循环直到衍生的子进程退出或被终止。

child_process 模块还提供了其他一些同步和异步的方法。 每个方法都是基于 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 惯用的异步编程模式。

每个方法都返回 ChildProcess 实例。 这些对象都实现了 EventEmitter 接口,允许父进程注册监听器函数,在子进程生命周期期间,当特定的事件发生时会调用这些函数。

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() 启动。 可以使用设置了 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,并缓冲产生的输出。 command 会被 shell 直接执行,所以 command 中的特殊字符(因shell而异)需要相应的处理:

exec('"/path/to/test file/test.sh" arg1 arg2');
// 使用双引号,则路径中的空格不会被当成多个参数。

exec('echo "The \\$HOME variable is $HOME"');
// 第一个 $HOME 会被转义,第二个则不会。

如果指定了 callback,则调用时会传入参数 (error, stdout, stderr)。 当成功时,error 会是 null。 当出错时,error 会是 Error 实例。 error.code 属性是子进程的退出码,error.signal 是终止进程的信号。 任何非 0 的退出码都会被视为出错。

传给 callbackstdoutstderr 包含子进程的输出。 默认情况下,Node.js 会将输出解码成 UTF-8 字符串。 encoding 用于指定解码 stdoutstderr 的字符编码。 如果 encoding'buffer' 或无效的字符编码,则传入 callback 的会是 Buffer

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

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

不像 POSIX 中的 exec(3)child_process.exec() 不会替换现有的进程,且使用 shell 来执行命令。

如果使用 util.promisify() 版本的方法,则 Promise 返回一个包含 stdoutstderr 的对象。

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.exec() 类似,除了默认不衍生 shell。 可执行的 file 会被直接衍生为一个新进程,这使得它比 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);
});

传给 callbackstdoutstderr 包含子进程的输出。 默认情况下,Node.js 会将输出解码成 UTF-8 字符串。 encoding 用于指定解码 stdoutstderr 的字符编码。 如果 encoding'buffer' 或无效的字符编码,则传入 callback 的会是 Buffer

如果使用 util.promisify() 版本的方法,则 Promise 返回一个包含 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 <string[]> 参数列表。
  • options <Object>

    • cwd <string> 子进程的当前工作目录。
    • env <Object> 环境变量键值对。
    • execPath <string> 用于创建子进程的可执行文件。
    • execArgv <string[]> 传给可执行文件的参数列表。默认为 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']
    • windowsVerbatimArguments <boolean> 在 Windows 上是否为参数加引号或转义。默认为 false
    • uid <number> 进程的用户标识,参见 setuid(2)
    • gid <number> 进程的群组标识,参见 setgid(2)
  • 返回: <ChildProcess>

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

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

默认情况下,child_process.fork() 会使用父进程中的 process.execPath 衍生新的 Node.js 实例。 optionsexecPath 可以指定要使用的可执行文件。

使用自定义的 execPath 启动的 Node.js 进程,会使用子进程的环境变量 NODE_CHANNEL_FD 中指定的文件描述符与父进程通信。

不像 POSIX 的 fork(2)child_process.fork() 不会克隆当前进程。

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

中英对照

使用 commandargs 衍生进程。

options 可以指定额外的选项,默认如下:

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

cwd 指定衍生的进程的工作目录。 如果没有指定,则默认为当前工作目录。

env 指定环境变量,默认为 process.env

env 中值为 undefined 的属性会被忽略。

例子,运行 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}`);
  }
});

例子,检测失败的 spawn

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

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

在 macOS 和 Linux 上会使用 argv[0] 的值作为进程的标题,而在 Windows 和 SunOS 上则使用 command 作为标题。

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

options.detached#

中英对照

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

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

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

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

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

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

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

subprocess.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 subprocess = spawn('prg', [], {
  detached: true,
  stdio: [ 'ignore', out, err ]
});

subprocess.unref();

options.stdio#

中英对照

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

options.stdio 可以是以下字符串之一:

  • 'pipe' - 等同于 ['pipe', 'pipe', 'pipe'](默认)。
  • 'ignore' - 等同于 ['ignore', 'ignore', 'ignore']
  • 'inherit' - 等同于 ['inherit', 'inherit', 'inherit'][0,1,2]

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

  1. 'pipe' - 创建一个子进程和父进程之间的管道。 管道的父端作为 child_process 对象的属性。 为 fd 创建的管道 0 - 2 分别作为 subprocess.stdinsubprocess.stdoutsubprocess.stderr
  2. 'ipc' - 创建一个用于父进程和子进程之间传递消息或文件描述符的 IPC 通道符。 一个 ChildProcess 最多只能有一个 IPC stdio 文件描述符。 设置该选项可启用 subprocess.send()。 如果子进程是一个 Node.js 进程,则一个已存在的 IPC 通道会在子进程中启用 process.send()process.disconnect(),同时也会启用 'disconnect''message' 事件。
  3. 'ignore' - 指示 Node.js 忽略子进程中的 fd。 由于 Node.js 总是会为它衍生的进程打开 fd 0 - 2,所以设置 fd 为 'ignore' 可以使 Node.js 打开 /dev/null 并将它附加到子进程的 fd 上。
  4. 'inherit' - 从父进程传入或传入父进程相应的 stdio 流。 前三个位置分别等同于 process.stdinprocess.stdoutprocess.stderr。 其他位置等同于 'ignore'
  5. <Stream> 对象 - 共享一个指向子进程的 tty、文件、socket 或管道的可读或可写流。 流的底层文件描述符在子进程中对应 stdio 数组索引的 fd。 该流必须有一个底层描述符(文件流直到 'open' 事件发生才需要)。
  6. 正整数 - 解析成在父进程打开的文件描述符。 与子进程共享,类似于 Stream 对象的共享。
  7. nullundefined - 使用默认值。 对于 stdio 的 fd 0、1 和 2(换言之,stdin、stdout 和 stderr),创建管道。 对于 fd 3 及以上,默认为 'ignore'
const { spawn } = require('child_process');

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

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

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

创建同步进程#

中英对照

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> 子进程的当前工作目录。
    • input <string> | <Buffer> | <TypedArray> | <DataView> 作为 stdin 传给衍生进程的值。指定该值会覆盖 stdio[0]
    • stdio <string> | <Array> 子进程的 stdio 配置。stderr 默认会输出到父进程的 stderr,除非指定了 stdio。默认为 'pipe'
    • env <Object> 环境变量键值对。
    • uid <number> 进程的用户标识。参见 setuid(2)
    • gid <number> 进程的群组标识。参见 setgid(2)
    • timeout <number> 允许进程运行的最长时间,以毫秒为单位。默认为 undefined
    • killSignal <string> | <integer> 用于杀死衍生进程的信号。默认为 'SIGTERM'
    • maxBuffer <number> stdout 或 stderr 允许的最大字节数。如果超过限制,则子进程会终止。参见 maxBuffer与Unicode。默认为 200*1024
    • encoding <string> 用于 stdio 输入和输出的字符编码。默认为 'buffer'
    • windowsHide <boolean> 是否隐藏子进程的控制台窗口。默认为 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.execFile() 的区别是,该方法需等到子进程完全关闭后才返回。 当发生超时且已发送 killSignal 时,该方法也需等到进程完全退出后才返回。

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

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

child_process.execSync(command[, options])#

中英对照

  • command <string> 运行的命令。
  • options <Object>

    • cwd <string> 子进程的当前工作目录。
    • input <string> | <Buffer> | <TypedArray> | <DataView> 传入衍生进程的 stdin。指定该值会覆盖 stdio[0]
    • stdio <string> | <Array> 子进程的 stdio 配置。stderr 默认会输出到父进程的 stderr,除非指定了 stdio。默认为 'pipe'
    • env <Object> 环境变量键值对。
    • 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。默认为 200*1024
    • encoding <string> 用于 stdio 输入和输出的字符编码。默认为 'buffer'
    • windowsHide <boolean> 是否隐藏子进程的控制台窗口。默认为 false
  • 返回: <Buffer> | <string> 命令的 stdout。

child_process.exec() 的区别是,该方法需等到子进程完全关闭后才返回。 当发生超时且已发送 killSignal 时,该方法也需等到进程完全退出后才返回。 如果子进程拦截并处理了 SIGTERM 信号且没有退出,则父进程会一直等待直到子进程退出。

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

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

中英对照

  • command <string> 运行的命令。
  • args <string[]> 参数列表。
  • options <Object>

    • cwd <string> 子进程的当前工作目录。
    • input <string> | <Buffer> | <TypedArray> | <DataView> 传入衍生进程的 stdin。指定该值会覆盖 stdio[0]
    • argv0 <string> 发送给子进程的 argv[0] 的值。如果没有指定,则设为 command 的值。
    • 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 允许的最大字节数。如果超过限制,则子进程会终止。参见 maxBuffer与Unicode。默认为 200*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 上是否为参数加引号或转义。如果指定了 shell,则自动设为 true。默认为 false
    • windowsHide <boolean> 是否隐藏子进程的控制台窗口。默认为 false
  • 返回: <Object>

child_process.spawn() 的区别是,该方法需等到子进程完全关闭后才返回。 当发生超时且已发送 killSignal 时,该方法也需等到进程完全退出后才返回。 如果子进程拦截并处理了 SIGTERM 信号且没有退出,则父进程会一直等待直到子进程退出。

ChildProcess 类#

中英对照

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

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

'close' 事件#

中英对照

  • code <number> 子进程的退出码。
  • signal <string> 终止子进程的信号。

当子进程的 stdio 流被关闭时触发。 与 'exit' 事件的区别是,多个进程可能共享同一 stdio 流。

'disconnect' 事件#

中英对照

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

'error' 事件#

中英对照

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

  1. 无法衍生进程;
  2. 无法杀死进程;
  3. 向子进程发送信息失败。

发生错误后,'exit' 事件可能会也可能不会触发。 如果同时监听了 'exit''error' 事件,可能会多次调用处理函数。

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

'exit' 事件#

中英对照

  • code <number> 子进程的退出码。
  • signal <string> 终止子进程的信号。

当子进程结束后时触发。 如果进程退出,则 code 是进程的最终退出码,否则为 null。 如果进程是收到的信号而终止,则 signal 是信号的名称,否则为 null。 这两个值至少有一个是非空的。

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

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

参见 waitpid(2)

'message' 事件#

中英对照

当子进程使用 process.send() 发送消息时触发。

消息通过序列化和解析传递,收到的消息可能跟发送的不完全一样。

subprocess.channel#

中英对照

  • <Object> 子进程的 IPC 通道的管道。

返回子进程的 IPC 通道的引用。 如果当前没有 IPC 通道,则返回 undefined

subprocess.connected#

中英对照

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

表明是否可以从子进程发送和接收消息。 当 subprocess.connectedfalse 时,则不能再发送或接收消息。

subprocess.disconnect()#

中英对照

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

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

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

subprocess.kill([signal])#

中英对照

向子进程发送信号。 如果没有指定参数,则进程会发送 '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 subprocess = spawn(
  'sh',
  [
    '-c',
    `node -e "setInterval(() => {
      console.log(process.pid, 'is alive')
    }, 500);"`
  ], {
    stdio: ['inherit', 'inherit', 'inherit']
  }
);

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

subprocess.killed#

中英对照

  • <boolean> 当调用 subprocess.kill() 发送信号给子进程后,该值会被设为 true

表明子进程是否已成功接收到 subprocess.kill() 的信号。 但并不表明子进程是否已被终止。

subprocess.pid#

中英对照

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

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

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

subprocess.ref()#

中英对照

调用 subprocess.unref() 之后再调用 subprocess.ref() 会还原已被移除的子进程引用计数,强迫父进程在退出自身之前等待子进程退出。

const { spawn } = require('child_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 cp = require('child_process');
const n = cp.fork(`${__dirname}/sub.js`);

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

// 使子进程输出: 子进程收到消息 { hello: 'world' }
n.send({ hello: 'world' });

子进程脚本 'sub.js' 如下:

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

// 使父进程输出: 父进程收到消息 { foo: 'bar', baz: null }
process.send({ foo: 'bar', baz: NaN });

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

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

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

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

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

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

例子:发送 server 对象#

中英对照

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

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

// 开启 server 对象,并发送该句柄。
const server = require('net').createServer();
server.on('connection', (socket) => {
  socket.end('被父进程处理');
});
server.listen(1337, () => {
  subprocess.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 { fork } = require('child_process');
const normal = fork('subprocess.js', ['normal']);
const special = fork('subprocess.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);

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

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

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

建议在子进程中的任何 message 处理程序都需要验证 socket 是否存在,因为连接可能会在它在发送给子进程的这段时间内被关闭。

subprocess.stderr#

中英对照

返回子进程的 stderr 可读流。

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

subprocess.stderrsubprocess.stdio[2] 的别名。 两个属性指向同一个值。

subprocess.stdin#

中英对照

返回子进程的 stdin 可写流。

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

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

subprocess.stdinsubprocess.stdio[0] 的别名。 两个属性指向同一个值。

subprocess.stdio#

中英对照

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

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

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

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

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.stdout#

中英对照

返回子进程的 stdout 可读流。

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

subprocess.stdoutsubprocess.stdio[1] 的别名。 两个属性指向同一个值。

subprocess.unref()#

中英对照

默认情况下,父进程会等待已解绑的子进程退出。 如果无需父进程等待,可使用 subprocess.unref() 退出 subprocess

const { spawn } = require('child_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('中文测试') 会发送 13 个 UTF-8 编码的字节到 stdout,尽管只有 4 个字符。

Shell 的要求#

中英对照

Shell 需要理解 UNIX 上的 -c 开关、或 Window 上的 /d /s /c。 在 Windows 上,命令行解析需要兼容 'cmd.exe'

Windows 默认的 Shell#

中英对照

尽管微软指定在根环境中 %COMSPEC% 必须包含 'cmd.exe' 的路径,但子进程并不一定遵循要求。 因此在衍生 shell 的 child_process 函数中,如果没有 process.env.ComSpec,则使用 'cmd.exe'