child_process - 子进程
# child_process - 子进程
该node:child_process
模块提供了以类似于但不完全相同的方式生成子进程的能力popen(3)
(opens new window)。此功能主要由函数提供child_process.spawn()
(opens new window):
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 的父进程与衍生的子进程之间会建立 stdin
、stdout
和 stderr
的管道。 数据能以非阻塞的方式在管道中流通。 有些程序会在内部使用行缓冲 I/O,虽然这并不影响 Node.js,但发送到子进程的数据可能无法被立即使用。
[child_process.spawn()
] 函数会异步地衍生子进程,且不会阻塞 Node.js 事件循环。 [child_process.spawnSync()
] 函数则以同步的方式提供同样的功能,但会阻塞事件循环,直到衍生的子进程退出或被终止。
child_process
模块还提供了其他一些同步和异步的可选函数。 每个函数都是基于 [child_process.spawn()
] 或 [child_process.spawnSync()
] 实现的。
- [
child_process.exec()
]: 衍生一个 shell 并在 shell 上运行命令,当完成时会传入stdout
和stderr
到回调函数。 - [
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 脚本,同步的函数 (opens new window)可能更方便。 但大多数情况下,同步的函数会明显影响性能,因为它会拖延事件循环直到衍生进程完成。
# 创建异步进程
[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()
] 启动。
可以使用设置了 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
child_process.exec(command[, options][, callback])
command
运行的命令,参数使用空格分隔。- options
cwd
子进程的当前工作目录。env
环境变量键值对。encoding
默认为'utf8'
。shell
执行命令的 shell。在 UNIX 上默认为'/bin/sh'
,在 Windows 上默认为process.env.ComSpec
。详见Shell的要求 (opens new window)与Windows默认的Shell (opens new window)。timeout
默认为0
。maxBuffer
stdout
或stderr
允许的最大字节数。默认为200*1024
。如果超过限制,则子进程会被终止。详见maxBuffer
与Unicode (opens new window)。killSignal
默认为'SIGTERM'
。uid
设置进程的用户标识,详见 setuid(2) (opens new window)。gid
设置进程的组标识,详见 setgid(2) (opens new window)。windowsHide
隐藏子进程的控制台窗口,常用于 Windows 系统。默认为false
。
- callback 进程终止时调用。
error
stdout
string (opens new window) | buffer (opens new window)stderr
string (opens new window) | buffer (opens new window)
- 返回: ChildProcess (opens new window)
衍生一个 shell 并在 shell 中执行 command
,且缓冲任何产生的输出。
传入函数的 command
字符串会被 shell 直接处理,特殊字符(因shell而异 (opens new window))需要相应处理:
exec('"/path/to/test file/test.sh" arg1 arg2');
// 使用双引号使路径中的空格不会被理解为多个参数。
exec('echo "The \\$HOME variable is $HOME"');
// 第一个 $HOME 会被转义,而第二个不会。
注意:不要把未经检查的用户输入传入到该函数。 任何包括 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
以外的任何退出码都被认为是一个错误。
传给回调的 stdout
和 stderr
参数会包含子进程的 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) (opens new window),child_process.exec()
不会替换现有的进程,且使用一个 shell 来执行命令。
如果调用该方法的 [util.promisify()
][] 版本,将会返回一个包含 stdout
和 stderr
的 Promise 对象。在出现错误的情况下,将返回 rejected 状态的 promise,拥有与回调函数一样的 error
对象,但附加了 stdout
和 stderr
属性。
例子
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
child_process.execFile(file[, args][, options][, callback])
file
string 要运行的可执行文件的名称或路径。args
string 字符串参数列表。- options
cwd
string 子进程的当前工作目录。env
object 环境变量键值对。encoding
string 默认为'utf8'
。timeout
number 默认为0
。maxBuffer
(opens new window) number stdout 或 stderr 允许的最大字节数。 默认为200*1024
。 如果超过限制,则子进程会被终止。 See caveat at [maxBuffer
and Unicode][].killSignal
string | number 默认为'SIGTERM'
。uid
number 设置该进程的用户标识。(详见 setuid(2) (opens new window))gid
number 设置该进程的组标识。(详见 setgid(2) (opens new window))windowsHide
boolean 是否隐藏在Windows系统下默认会弹出的子进程控制台窗口。 默认为:false
。windowsVerbatimArguments
boolean 决定在Windows系统下是否使用转义参数。 在Linux平台下会自动忽略,当指令shell
存在的时该属性将自动被设置为true。默认为:false
。
- callback 当进程终止时调用,并带上输出。
error
stdout
stderr
- 返回: ChildProcess (opens new window)
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);
});
传给回调的 stdout
和 stderr
参数会包含子进程的 stdout 和 stderr 的输出。 默认情况下,Node.js 会解码输出为 UTF-8,并将字符串传给回调。 encoding
选项可用于指定用于解码 stdout 和 stderr 输出的字符编码。 如果 encoding
是 'buffer'
、或一个无法识别的字符编码,则传入 Buffer
对象到回调函数。
如果调用该方法的 [util.promisify()
][] 版本, 它会返回一个拥有 stdout
和 stderr
属性的 Promise 对象. 在发生错误的情况下, 返回一个 rejected 状态的 promise, 拥有与回调 函数一样的 error
对象, 但是附加了 stdout
和 stderr
这两个属性.
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
child_process.fork(modulePath[, args][, options])
modulePath
string (opens new window) 要在子进程中运行的模块。args
array (opens new window) 字符串参数列表。options
cwd
string (opens new window) 子进程的当前工作目录。env
object (opens new window) 环境变量键值对。execPath
string (opens new window) 用来创建子进程的执行路径。execArgv
array (opens new window) 要传给执行路径的字符串参数列表。默认为process.execArgv
。silent
boolean (opens new window) 如果为true
,则子进程中的 stdin、 stdout 和 stderr 会被导流到父进程中,否则它们会继承自父进程,详见 [child_process.spawn()
] 的 [stdio
] 中的'pipe'
和'inherit'
选项。 默认:false
。stdio
array (opens new window) | string (opens new window) 详见 [child_process.spawn()
] 的 [stdio
]。 当提供了该选项,则它会覆盖silent
。 如果使用了数组变量,则该数组必须包含一个值为'ipc'
的子项,否则会抛出错误。 例如[0, 1, 2, 'ipc']
。windowsVerbatimArguments
boolean (opens new window) 决定在Windows系统下是否使用转义参数。 在Linux平台下会自动忽略。默认值:false
。uid
number (opens new window) 设置该进程的用户标识。(详见 setuid(2) (opens new window))gid
number (opens new window) 设置该进程的组标识。(详见 setgid(2) (opens new window))
该child_process.fork()
方法是一种特殊情况, child_process.spawn()
(opens new window)专门用于生成新的 Node.js 进程。像child_process.spawn()
(opens new window),ChildProcess
(opens new window)返回一个对象。返回的ChildProcess
(opens new window)将有一个额外的内置通信通道,允许消息在父子之间来回传递。subprocess.send()
(opens new window)详情请见。
请记住,生成的 Node.js 子进程独立于父进程,但在两者之间建立的 IPC 通信通道除外。每个进程都有自己的内存,有自己的 V8 实例。由于需要额外的资源分配,因此不建议生成大量子 Node.js 进程。
默认情况下,将使用父进程的child_process.fork()
生成新的 Node.js 实例 。对象中的属性 process.execPath
(opens new window)允许使用替代执行路径。execPath``options
使用自定义启动的 Node.js 进程将使用使用子进程上的execPath
环境变量标识的文件描述符 (fd) 与父进程通信。NODE_CHANNEL_FD
不像fork(2)
(opens new window)POSIX 系统调用,child_process.fork()
不克隆当前进程。
shell
中可用的选项不受支持child_process.spawn()
(opens new window), child_process.fork()
如果设置将被忽略。
如果signal
启用该选项,调用.abort()
相应的 AbortController
类似于调用.kill()
子进程,除了传递给回调的错误将是AbortError
:
if (process.argv[2] === 'child') {
setTimeout(() => {
console.log(`Hello from ${process.argv[2]}!`);
}, 1_000);
} else {
const { fork } = require('child_process');
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
}
# child_process.spawn
child_process.spawn(command[, args][, options])
command
string (opens new window)要运行的命令。args
string (opens new window)字符串参数列表。options
<对象> (opens new window)cwd
<字符串> (opens new window) | (opens new window)子进程的当前工作目录。env
object (opens new window)环境键值对。默认值:process.env
.argv0
string (opens new window)argv[0]
显式设置发送给子进程的值。command
如果未指定,这将设置为。stdio
(opens new window) | (opens new window)孩子的 stdio 配置(参见 参考资料options.stdio
(opens new window))。detached
boolean (opens new window)准备子进程独立于其父进程运行。具体行为取决于平台,请参阅 参考资料options.detached
(opens new window))。uid
number (opens new window)设置进程的用户身份(参见 参考资料setuid(2)
(opens new window))。gid
number (opens new window)设置进程的组标识(请参阅参考资料setgid(2)
(opens new window))。serialization
string (opens new window)指定用于在进程之间发送消息的序列化类型。可能的值为'json'
和'advanced'
。有关详细信息,请参阅高级序列化。 (opens new window)默认值:'json'
.shell
<布尔> (opens new window) | string (opens new window)如果true
,则command
在 shell 内部运行。'/bin/sh'
在 Unix 和process.env.ComSpec
Windows 上使用 。可以将不同的 shell 指定为字符串。请参阅Shell 要求 (opens new window)和 默认 Windows shell (opens new window)。默认值:(false
无外壳)。windowsVerbatimArguments
boolean (opens new window)在 Windows 上没有引用或转义参数。在 Unix 上忽略。当被指定并且是 CMD时,这被设置为true
自动。默认值: .shell
false
windowsHide
boolean (opens new window)隐藏通常在 Windows 系统上创建的子进程控制台窗口。默认值:false
.signal
abortsignal) (opens new window)允许使用 AbortSignal 中止子进程。timeout
number (opens new window)允许进程运行的最长时间,以毫秒为单位。默认值:undefined
.killSignal
<字符串> (opens new window) | (opens new window)当派生进程将被超时或中止信号终止时使用的信号值。默认值:'SIGTERM'
.
- 返回:<子进程> (opens new window)
该child_process.spawn()
方法使用给定的生成一个新进程 command
,命令行参数在args
. 如果省略,args
则默认为空数组。
如果shell
启用该选项,请不要将未经过滤的用户输入传递给此函数。任何包含 shell 元字符的输入都可用于触发任意命令执行。
第三个参数可用于指定其他选项,具有以下默认值:
const defaults = {
cwd: undefined,
env: process.env
};
用于cwd
指定生成进程的工作目录。如果没有给出,默认是继承当前工作目录。如果给定,但路径不存在,子进程会发出错误ENOENT
并立即退出。ENOENT
当命令不存在时也会发出。
用于env
指定对新进程可见的环境变量,默认为process.env
(opens new window).
undefined
中的值env
将被忽略。
运行ls -lh /usr
、捕获stdout
、stderr
和退出代码的示例:
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.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${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.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('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 当前会在启动时覆盖argv[0]
,process.execPath
因此 process.argv[0]
在 Node.js 子进程中不会匹配从父 进程argv0
传递给的参数,而是使用属性检索它。spawn``process.argv0
如果signal
启用该选项,调用.abort()
相应的 AbortController
类似于调用.kill()
子进程,除了传递给回调的错误将是AbortError
:
const { spawn } = require('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)
(opens new window)有关详细信息,请参阅。
默认情况下,父母将等待分离的孩子退出。要防止父级等待给定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
该options.stdio
选项用于配置在父进程和子进程之间建立的管道。默认情况下,孩子的 stdin、stdout 和 stderr 被重定向到对象上相应的subprocess.stdin
(opens new window)、 subprocess.stdout
(opens new window)和subprocess.stderr
(opens new window)流 ChildProcess
(opens new window)。这相当于设置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。fds 0、1 和 2 分别对应于 stdin、stdout 和 stderr。可以指定额外的 fds 以在父子之间创建额外的管道。该值为以下之一:
'pipe'
:在子进程和父进程之间创建管道。管道的父端作为对象的属性公开给父child_process
级subprocess.stdio[fd\]
(opens new window)。为 fds 0、1 和 2 创建的管道也可分别用作subprocess.stdin
(opens new window)、subprocess.stdout
(opens new window)和subprocess.stderr
(opens new window)。'overlapped'``'pipe'
:除了在FILE_FLAG_OVERLAPPED
句柄上设置标志外,其他相同。这对于子进程的 stdio 句柄上的重叠 I/O 是必需的。 有关详细信息,请参阅 文档。 (opens new window)'pipe'
这与非 Windows 系统完全相同。'ipc'
:创建一个 IPC 通道,用于在父子之间传递消息/文件描述符。AChildProcess
(opens new window)最多可以有一个 IPC stdio 文件描述符。设置此选项可启用该subprocess.send()
(opens new window)方法。如果子进程是 Node.js 进程,IPC 通道的存在将启用process.send()
(opens new window)和process.disconnect()
(opens new window)方法,以及子进程中的事件'disconnect'
(opens new window)。'message'
(opens new window)process.send()
(opens new window) 不支持以任何方式访问 IPC 通道 fd或将 IPC 通道与非 Node.js 实例的子进程一起使用。'ignore'
: 指示 Node.js 忽略子节点中的 fd。虽然 Node.js 将始终为其生成的进程打开 fds 0、1 和 2,但将 fd 设置为'ignore'
将导致 Node.js 打开/dev/null
并将其附加到子进程的 fd。'inherit'
: 通过相应的 stdio 流传入/传出父进程。在前三个位置,这分别相当于process.stdin
、process.stdout
、 和process.stderr
。在任何其他位置,等同于'ignore'
.stream (opens new window)对象:与子进程共享引用 tty、文件、套接字或管道的可读或可写流。流的底层文件描述符在子进程中被复制到与数组中的索引对应的 fd
stdio
。'open'
流必须有一个底层描述符(文件流在事件发生之前没有)。正整数:整数值被解释为当前在父进程中打开的文件描述符。它与子进程共享,类似于共享对象的方式。 (opens new window)Windows 不支持传递套接字。
null
,undefined
: 使用默认值。对于 stdio fds 0、1 和 2(换句话说,stdin、stdout 和 stderr),创建了一个管道。对于 fd 3 及更高版本,默认值为'ignore'
.
const { spawn } = require('child_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'
(opens new window)程序或'message'
(opens new window)事件。这允许子进程正常退出,而进程不会被打开的 IPC 通道保持打开状态。
在类 Unix 操作系统上,该child_process.spawn()
(opens new window)方法在将事件循环与子进程解耦之前同步执行内存操作。具有大量内存占用的应用程序可能会发现频繁 child_process.spawn()
(opens new window)调用成为瓶颈。有关详细信息,请参阅V8 问题 7381 (opens new window)。
另见:child_process.exec()
(opens new window)和child_process.fork()
(opens new window)。