what is Execa
Execa 是一个 Node.js 库,可以替代 Node.js 的原生 child_process 模块,用于执行外部命令。Execa拥有更好的性能、可靠性和易用性,支持流式传输、输出控制、交互式 shell 等功能,并跨平台兼容 Windows、macOS 和 Linux 等操作系统。同时,Execa 还支持 Promise API,提供更好的异步控制和异常处理机制。使用 Execa 可以简化发现和解决常见的子进程处理问题,是 Node.js 开发中非常有用的工具之一。
how to use
- 安装 Execa
在项目中使用 Execa 库需要先安装它,可以使用 npm 进行安装:
npm install --save dev execa
- 执行命令
在项目中需要执行外部命令时,可以使用 execa 执行它。比如下面的示例中执行了ls命令并将返回的结果输出到控制台:
const execa = require('execa');
(async () => {
const { stdout } = await execa('ls');
console.log(stdout);
})();
- 传递参数
Execa允许传递多个参数和选项给外部命令,这些参数可以通过数组或者对象传递。下面是一个示例,它传递了两个参数给ls命令:
const execa = require('execa');
(async () => {
const { stdout } = await execa('ls', ['-lh', '/usr']);
console.log(stdout);
})();
- 控制进程STDOUT和STDERR
Execa允许控制进程的STDOUT和STDERR输出。可以设置stdout和stderr选项,让它们将输出写入流中,你也可以将它们设置为'pipe',这样它们就会返回到Promise中,以便进行进一步处理。下面是一个示例,将标准错误输出重定向到标准输出:
const execa = require('execa');
(async () => {
const { stdout } = await execa('ls', ['-lh', '/InvalidPath'], {
stderr: 'stdout',
});
console.log(stdout);
})();
- 使用Promise Api
Execa也支持Promise API,这意味着它可以更好地处理异步回调和异常。下面是一个示例,仅在命令执行成功时处理回调函数:
const execa = require('execa');
const child = execa('echo', ['hello'], { stdio: 'inherit' });
child
.then(() => {
console.log('Command succeeded');
})
.catch((error) => {
console.error('Command failed with error', error);
});
- 进程间通信
Execa还支持进程间通信,可以使用stdio选项设置输入输出流。此选项可以采取值'pipe'、'inherit'、'ignore',分别表示使用IPC通信、继承主进程的输入输出流、忽略输入输出流。在以下示例中,进程之间使用stdio共享输入和输出流:
const execa = require('execa');
const child = execa('ls', ['-lh'], {
stdio: 'inherit',
});
console.log(child.pid);
当使用Execa执行外部命令时,有时候需要与该进程进行交互,比如向进程输入数据并读取输出。进程间通信(Inter-Process Communication,IPC)就是通过输入和输出流来实现这种交互操作。Execa提供了stdio选项来表示输入和输出流的使用方式,这三种方式分别是:
-
pipe (使用IPC通信,默认值)
表示创建一个父子进程之间的管道,父进程通过这个管道向子进程输入数据,子进程通过这个管道向父进程输出结果。 这种方式可以很好地处理输入输出交互问题。
-
inherit
表示子进程继承了父进程的输入输出流,子进程直接打印到父进程的输出终端上,父进程可以直接输入到子进程的命令行shell中,并观察执行结果。 一般建议使用inherit模式,以便直观地查看进程操作结果。
-
ignore
表示忽略输入输出流,不向进程输入数据,也不从进程读取数据。对于不需要交互的命令或操作,可以使用这种方式。
下面是一个示例,演示了在子进程中使用stdin.write()来读取父进程的输入,使用process.stdout.write()向父进程输出的例子,使用pipe方式进行IPC通信:
const { spawn } = require('child_process');
const subprocess = spawn('node', ['ipc-subprocess.js'], {
stdio: ['pipe', 'pipe', 'pipe', 'ipc']
});
subprocess.on('message', (message) => {
console.log(`Got message: ${message}`);
});
process.stdin.pipe(subprocess.stdin);
subprocess.stdout.pipe(process.stdout);
process.stdin.write('hello subprocess');
process.stdin.end();
上面的代码中spawn了一个子进程“node ipc-subprocess.js”,并使用pipe方式配置输入输出流。然后,将父进程的stdout管道输出流,发送到子进程的stdin管道输入流,而将子进程的stdout管道输出流,发送到父进程的stdin管道输入流。数据的输入和输出都是通过管道交互完成的。
在子进程的ipc-subprocess.js中使用process.stdin.on('data', ...)来监听父进程输入的数据,使用process.send()来向其它进程发送消息:
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
chunk = chunk.trim();
console.log(`Subprocess got data: ${chunk}`);
if (chunk === 'exit') {
console.log('Subprocess exit');
process.exit(0);
} else {
process.send(`Hello, "${chunk}" (pid: ${process.pid})`);
}
});
上面的代码中监听了stdin输入的数据,如果父进程输入为“exit”,则直接退出当前进程。否则,向父进程发送一个消息“Hello, "{process.pid})”。
处理完成后,调用process.send()来发送消息到其它进程。在这里发送的消息是一个字符串,子进程在收到这个消息后会将其在控制台上输出。
这个IPC通信示例是一个简单的一对一交互过程,Execa 还提供了调用外部进程的setTimeout、Promise链式操作、信号处理等更实用的功能,可以更好地处理进程间通信问题。
execa 方法的参数如下:
- command {string}: 执行的命令。例如 ls。
- [args] {string[]}: 命令参数列表,例如 ["-l", "./"]。
- [options] {Object}: 可选选项。包括:
- cwd {string}: 运行命令时使用的当前工作目录。默认为 process.cwd()。
- env {Object}: 用于覆盖 process.env 的环境变量对象。
- extendEnv {boolean}: 是否在子进程中扩展 process.env 对象。
- shell {boolean|string}: 是否在操作系统的 shell 中运行命令。默认为 false。可以是一个字符串,该字符串将作为 shell 的路径。
- stdio {string|Array}: 子进程的标准流。默认为 'pipe'。可以是长度为 3 的数组,分别表示子进程的 stdin、stdout 和 stderr,也可以是一个字符串,表示将所有流合并到一个流中。字符串的有效值为 'pipe'、'ignore' 或 'inherit'。
- input {string|Buffer}: 向 stdin 流写入的内容。
- buffer {boolean}: 是否将 stdout 和 stderr 流中的数据存储在内存中。如果为 false,则将流传递给父进程中的流。默认为 true。
- maxBuffer {number}: 允许的 stdout 或 stderr 流中的最大数据量(以字节为单位)。如果超出了此限制,则子进程将被终止并抛出一个错误。默认为 10MB。
- killSignal {string|number}: 终止子进程使用的信号。可以是信号名称的字符串(例如 SIGKILL),也可以是信号的数值。默认为 SIGTERM。
- preferLocal {boolean}: 是否优先使用项目中安装的二进制文件而不是全局安装的二进制文件。默认为 true。
- localDir {string}: 用于查找本地安装的二进制文件的目录。 默认为 process.cwd()。
- reject {boolean}: 是否在命令返回非零退出代码时拒绝 Promise。默认为 true。
- timeout {number}: 子进程的超时时间(以毫秒为单位)。如果超时,则进程将被终止并抛出超时错误。默认为 0(无超时时间)。