child_process - 子进程
该node: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 的父进程与衍生的子进程之间会建立 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 脚本,同步的函数可能更方便。 但大多数情况下,同步的函数会明显影响性能,因为它会拖延事件循环直到衍生进程完成。
创建异步进程
[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 上默认为p
。详见Shell 的要求与Windows 默认的 Shell。rocess.env.ComSpec timeout
默认为0
。maxBuffer
stdout
或stderr
允许的最大字节数。默认为200*1024
。如果超过限制,则子进程会被终止。详见maxBuffer
与 Unicode。killSignal
默认为'SIGTERM'
。uid
设置进程的用户标识,详见 setuid(2)。gid
设置进程的组标识,详见 setgid(2)。windowsHide
隐藏子进程的控制台窗口,常用于 Windows 系统。默认为false
。
- callback 进程终止时调用。
- 返回: ChildProcess
衍生一个 shell 并在 shell 中执行 command
,且缓冲任何产生的输出。
传入函数的 command
字符串会被 shell 直接处理,特殊字符(因 shell 而异)需要相应处理:
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),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
number stdout 或 stderr 允许的最大字节数。 默认为200*1024
。 如果超过限制,则子进程会被终止。 See caveat at [maxBuffer
and Unicode][].killSignal
string | number 默认为'SIGTERM'
。uid
number 设置该进程的用户标识。(详见 setuid(2))gid
number 设置该进程的组标识。(详见 setgid(2))windowsHide
boolean 是否隐藏在 Windows 系统下默认会弹出的子进程控制台窗口。 默认为:false
。windowsVerbatimArguments
boolean 决定在 Windows 系统下是否使用转义参数。 在 Linux 平台下会自动忽略,当指令shell
存在的时该属性将自动被设置为 true。默认为:false
。
- callback 当进程终止时调用,并带上输出。
error
stdout
stderr
- 返回: ChildProcess
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 要在子进程中运行的模块。args
array 字符串参数列表。options
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']
。windowsVerbatimArguments
boolean 决定在 Windows 系统下是否使用转义参数。 在 Linux 平台下会自动忽略。默认值:false
。uid
number 设置该进程的用户标识。(详见 setuid(2))gid
number 设置该进程的组标识。(详见 setgid(2))
该child_process.fork()
方法是一种特殊情况, child_process.spawn()
专门用于生成新的 Node.js 进程。像child_process.spawn()
,ChildProcess
返回一个对象。返回的ChildProcess
将有一个额外的内置通信通道,允许消息在父子之间来回传递。subprocess.send()
详情请见。
请记住,生成的 Node.js 子进程独立于父进程,但在两者之间建立的 IPC 通信通道除外。每个进程都有自己的内存,有自己的 V8 实例。由于需要额外的资源分配,因此不建议生成大量子 Node.js 进程。
默认情况下,将使用父进程的child_process.fork()
生成新的 Node.js 实例 。对象中的属性 process.execPath
允许使用替代执行路径。execPath``options
使用自定义启动的 Node.js 进程将使用使用子进程上的execPath
环境变量标识的文件描述符 (fd) 与父进程通信。NODE_CHANNEL_FD
不像fork(2)
POSIX 系统调用,child_process.fork()
不克隆当前进程。
shell
中可用的选项不受支持child_process.spawn()
, 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要运行的命令。args
string字符串参数列表。options
对象cwd
字符串 | url_the_whatwg_url_api子进程的当前工作目录。env
object环境键值对。默认值:p
.rocess.env argv0
stringargv[0]
显式设置发送给子进程的值。command
如果未指定,这将设置为。stdio
array | 孩子的 stdio 配置(参见 参考资料options.stdio
)。detached
boolean准备子进程独立于其父进程运行。具体行为取决于平台,请参阅 参考资料options.detached
)。uid
number设置进程的用户身份(参见 参考资料setuid(2)
)。gid
number设置进程的组标识(请参阅参考资料setgid(2)
)。serialization
string指定用于在进程之间发送消息的序列化类型。可能的值为'json'
和'advanced'
。有关详细信息,请参阅高级序列化。默认值:'json'
.shell
布尔 | string如果true
,则command
在 shell 内部运行。'/bin/sh'
在 Unix 和p
Windows 上使用 。可以将不同的 shell 指定为字符串。请参阅Shell 要求和 默认 Windows shell。默认值:(rocess.env.ComSpec false
无外壳)。windowsVerbatimArguments
boolean在 Windows 上没有引用或转义参数。在 Unix 上忽略。当被指定并且是 CMD 时,这被设置为true
自动。默认值: .shell
false
windowsHide
boolean隐藏通常在 Windows 系统上创建的子进程控制台窗口。默认值:false
.signal
abortsignal)允许使用 AbortSignal 中止子进程。timeout
number允许进程运行的最长时间,以毫秒为单位。默认值:undefined
.killSignal
字符串 | 当派生进程将被超时或中止信号终止时使用的信号值。默认值:'SIGTERM'
.
- 返回:子进程
该child_process.spawn()
方法使用给定的生成一个新进程 command
,命令行参数在args
. 如果省略,args
则默认为空数组。
如果shell
启用该选项,请不要将未经过滤的用户输入传递给此函数。任何包含 shell 元字符的输入都可用于触发任意命令执行。
第三个参数可用于指定其他选项,具有以下默认值:
const defaults = {
cwd: undefined,
env: process.env,
};
用于cwd
指定生成进程的工作目录。如果没有给出,默认是继承当前工作目录。如果给定,但路径不存在,子进程会发出错误ENOENT
并立即退出。ENOENT
当命令不存在时也会发出。
用于env
指定对新进程可见的环境变量,默认为p
.
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)
有关详细信息,请参阅。
默认情况下,父母将等待分离的孩子退出。要防止父级等待给定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
、 subprocess.stdout
和subprocess.stderr
流 ChildProcess
。这相当于设置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\]
。为 fds 0、1 和 2 创建的管道也可分别用作subprocess.stdin
、subprocess.stdout
和subprocess.stderr
。'overlapped'``'pipe'
:除了在FILE_FLAG_OVERLAPPED
句柄上设置标志外,其他相同。这对于子进程的 stdio 句柄上的重叠 I/O 是必需的。 有关详细信息,请参阅 文档。'pipe'
这与非 Windows 系统完全相同。'ipc'
:创建一个 IPC 通道,用于在父子之间传递消息/文件描述符。AChildProcess
最多可以有一个 IPC stdio 文件描述符。设置此选项可启用该subprocess.send()
方法。如果子进程是 Node.js 进程,IPC 通道的存在将启用process.send()
和process.disconnect()
方法,以及子进程中的事件'disconnect'
。'message'
process.send()
不支持以任何方式访问 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对象:与子进程共享引用 tty、文件、套接字或管道的可读或可写流。流的底层文件描述符在子进程中被复制到与数组中的索引对应的 fd
stdio
。'open'
流必须有一个底层描述符(文件流在事件发生之前没有)。正整数:整数值被解释为当前在父进程中打开的文件描述符。它与子进程共享,类似于共享对象的方式。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'
程序或'message'
事件。这允许子进程正常退出,而进程不会被打开的 IPC 通道保持打开状态。
在类 Unix 操作系统上,该child_process.spawn()
方法在将事件循环与子进程解耦之前同步执行内存操作。具有大量内存占用的应用程序可能会发现频繁 child_process.spawn()
调用成为瓶颈。有关详细信息,请参阅V8 问题 7381。