Skip to content
本页内容

child_process - 子进程

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

js
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.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()] 启动。

可以使用设置了 shell 选项的 [child_process.spawn()]、或使用 [child_process.exec()]、或衍生 cmd.exe 并将 .bat.cmd 文件作为参数传入(也就是 shell 选项和 [child_process.exec()] 所做的工作)。

如果脚本文件名包含空格,则需要加上引号。

js
// 仅限 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}`);
});
js
// 或
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 的要求Windows 默认的 Shell
    • timeout 默认为 0
    • maxBuffer stdoutstderr 允许的最大字节数。默认为 200*1024。如果超过限制,则子进程会被终止。详见 maxBuffer与 Unicode
    • killSignal 默认为 'SIGTERM'
    • uid 设置进程的用户标识,详见 setuid(2)
    • gid 设置进程的组标识,详见 setgid(2)
    • windowsHide隐藏子进程的控制台窗口,常用于 Windows 系统。默认为 false
  • callback 进程终止时调用。
  • 返回: ChildProcess

衍生一个 shell 并在 shell 中执行 command,且缓冲任何产生的输出。

传入函数的 command 字符串会被 shell 直接处理,特殊字符(因 shell 而异)需要相应处理:

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

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

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

js
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 参数可以作为第二个参数传入,用于自定义如何衍生进程。 默认的选项是:

js
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()][] 版本,将会返回一个包含 stdoutstderr 的 Promise 对象。在出现错误的情况下,将返回 rejected 状态的 promise,拥有与回调函数一样的 error 对象,但附加了 stdoutstderr 属性。

例子

js
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 重定向和文件查找这样的行为。

js
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 对象. 在发生错误的情况下, 返回一个 rejected 状态的 promise, 拥有与回调 函数一样的 error 对象, 但是附加了 stdoutstderr 这两个属性.

js
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)
  • 返回: class_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()生成新的 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

js
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环境键值对。默认值: process.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 和process.env.ComSpecWindows 上使用 。可以将不同的 shell 指定为字符串。请参阅Shell 要求默认 Windows shell默认值:( 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 元字符的输入都可用于触发任意命令执行。

第三个参数可用于指定其他选项,具有以下默认值:

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

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

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

undefined中的值env将被忽略。

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

js
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

js
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

js
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

js
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.detachedtrue可以让子进程在父进程退出后继续运行。孩子将有自己的控制台窗口。一旦为子进程启用,它就不能被禁用。

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

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

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

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

js
const { spawn } = require("child_process");

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

subprocess.unref();

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

js
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.stdinsubprocess.stdoutsubprocess.stderrChildProcess。这相当于设置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 以在父子之间创建额外的管道。该值为以下之一:

  1. 'pipe':在子进程和父进程之间创建管道。管道的父端作为对象的属性公开给父 child_processsubprocess.stdio[fd\]。为 fds 0、1 和 2 创建的管道也可分别用作subprocess.stdinsubprocess.stdoutsubprocess.stderr

  2. 'overlapped'``'pipe':除了在FILE_FLAG_OVERLAPPED句柄上设置标志外,其他相同。这对于子进程的 stdio 句柄上的重叠 I/O 是必需的。 有关详细信息,请参阅 文档。'pipe'这与非 Windows 系统完全相同。

  3. 'ipc':创建一个 IPC 通道,用于在父子之间传递消息/文件描述符。AChildProcess最多可以有一个 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 将始终为其生成的进程打开 fds 0、1 和 2,但将 fd 设置为'ignore'将导致 Node.js 打开/dev/null并将其附加到子进程的 fd。

  5. 'inherit': 通过相应的 stdio 流传入/传出父进程。在前三个位置,这分别相当于 process.stdinprocess.stdout、 和process.stderr。在任何其他位置,等同于'ignore'.

  6. stream对象:与子进程共享引用 tty、文件、套接字或管道的可读或可写流。流的底层文件描述符在子进程中被复制到与数组中的索引对应的 fd stdio'open'流必须有一个底层描述符(文件流在事件发生之前没有)。

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

  8. null, undefined: 使用默认值。对于 stdio fds 0、1 和 2(换句话说,stdin、stdout 和 stderr),创建了一个管道。对于 fd 3 及更高版本,默认值为'ignore'.

js
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

另见:child_process.exec()child_process.fork()