execa:Node.js 新一代 child_process 强力替代方案
英文标题:execa NodeJs新一代child_process的强力替代品
execa 是 Node.js 原生 `child_process` 模块的增强替代品,为执行系统命令提供了简单、可靠且对新手友好的 Promise API。它支持直接通过 `await` 获取 `stdout`、`stderr` 和退出码,并提供包含完整上下文的异常对象,减少回调和 Buffer 处理的复杂度。该库通过数组参数与 `$` 模板字符串自动转义机制降低 Shell 注入风险,同时覆盖同步执行、流式输出、管道、超时、取消任务等常见场景。对于需要在 Node.js 脚本、构建工具、CLI 或自动化流程中调用外部命令的开发者,execa 提供了比原生 API 更易用的封装。
打开原文相关 2 · 机会 2
10.1 KB
中文
execa
execa 是 Node.js 官方 child_process 模块的增强替代品。它将 JavaScript 中较为繁琐的“执行命令”操作,封装为简单、可靠、对新手友好的 Promise API。
与原生写法相比,优势明显:
| 执行并获取输出 | 需要自行封装 Promise | 可直接 await,返回 {stdout, stderr} |
|---|---|---|
| 捕获错误 | 需要判断回调中的 error 对象 |
直接使用 catch,错误对象信息完整 |
| 二进制输出 | Buffer 处理繁琐 | stdout 自动按 UTF-8 解码为字符串 |
快速开始:
npm install execa
import { execa } from 'execa'
const { stdout } = await execa('echo',['hello world'])
console.log(stdout);
try{
await execa('unknown-command');
} catch(error){
console.log(error.shortMessage);
console.log(error.exitCode);
}
await 会返回 {stdout, stderr};无论命令成功或失败,都可以获取对应的返回结果与错误信息。
核心 API
execa('command',[argument],[options])
这是最常用的形式。通过数组传参,无需拼接字符串,可天然规避 Shell 注入风险:
await execa('git', ['log', '--oneline', '-5']);
await execa('echo', ['$HOME'], { shell: true });
$ 模板字符串命令
虽然 execa 的底层参数形式为数组,但它提供了 $ 语法糖,让你能够使用模板字符串,同时自动完成参数转义:
import { $ } from 'execa';
await $`echo hello`;
await $`git commit -m ${'feat: 新功能'}`;
const { stdout } = await $`ls -al`;
execaSync('command',[arguments],[options])
同步版本,适合脚本初始化和简单场景:
import { execaSync } from 'execa';
const { stdout } = execaSync('npm', ['--version']);
console.log(stdout);
结果对象
await 后返回的对象包含:
stdout— 标准输出字符串stderr— 错误输出字符串exitCode— 退出码failed— 布尔值,表示是否执行失败command— 实际执行的完整命令,便于记录日志timedOut、killed、isCanceled等状态标记
execa 的异常对象信息非常完整(继承自 Error):
error.stdout/error.stderr— 失败时的标准输出与错误输出error.exitCodeerror.message— 包含命令、参数和退出码error.shortMessage— 精简错误信息error.failed—trueerror.timedOut/error.isCanceled/error.killed— 各类失败原因标记error.command— 实际执行的完整命令字符串
同步版 execa 返回的是 result 对象。
常用配置项(options)
| 选项 | 作用 | 示例 |
|---|---|---|
cwd |
指定工作目录 | { cwd: '/path/to/project' } |
env |
注入环境变量,默认继承父进程 | { env: { FOO: 'bar' } } |
shell |
使用 Shell 执行,支持管道、重定向和通配符 | { shell: true } |
timeout |
超时后强制终止,单位为毫秒 | { timeout: 5000 } |
input |
向子进程的 stdin 写入数据 | { input: 'hello stdin' } |
stdio |
控制输入输出流向 | { stdio: 'inherit' } 可直接输出到终端 |
reject |
失败时是否抛出异常;设为 false 时不抛错,而是返回结果对象 |
{ reject: false } |
buffering |
关闭输出缓冲,实现实时流式读取(v7+) | { buffering: false },配合 stream.pipe()/catch() |
windowsHide |
在 Windows 下隐藏子进程窗口 | 默认 true |
加载配置与默认值:
const res = await execa('node', ['app.js'], {
cwd: path.resolve('./server'),
timeout: 10000,
env: { NODE_ENV: ' 'production' },
});
进阶能力
1. 管道(stream 链式)
import { execa } from 'execa';
const psResult = await execa('ps', ['aux'], { stripFinalNewline: false });
await execa('grep', ['node'], { input: psResult.stdout });
2. 流式实时输出(实时看到命令逐行输出,而非最后一次性返回)
const subprocess = execa('npm', ['run', 'build'], { buffering: false });
subprocess.stdout.pipe(process.stdout);
subprocess.stderr.pipe(process.stderr);
subprocess.catch((error) => {
console.error(error.stderr);
});
3. 取消任务(kill / cancel)
const subprocess = execa('node', ['long-task.js']);
setTimeout(() => subprocess.kill('SIGTERM'), 3000);
await subprocess;
import { execa } from 'execa';
const controller = new AbortController();
setTimeout(() => controller.abort(), 3000);
await execa('node', ['long-task.js'], { signal: controller.signal });
4. 超时
try {
await execa('ping', ['-t', '127.0.0.1'], { timeout: 2000 });
} catch (error) {
console.log(error.timedOut);
}
5. 拒绝策略(reject: false)
如果不希望失败时抛出异常,只想根据退出码进行判断:
const result = await execa('my-tool', args, { reject: false });
console.log(result.exitCode, result.failed, result.stderr);
6. 执行完整 Shell 脚本
const { stdout } = await execa('bash', ['-c', 'echo a && echo b']);
English / 原文
execa
execa 是 NodeJs 官方 child_process 模块的增强替代品。把 JS 里"执行命令"这件麻烦事,做成简单、可靠、对新手友好 的 Promise API
对比原生写法,优势立现:
| 执行并拿输出 | 要自己包 Promise | 直接 await 返回 {stdout, stderr} |
|---|---|---|
| 捕获错误 | 判断回调 error 对象 | catch 即可,错误对象信息齐全 |
| 二进制输出 | Buffer 处理繁琐 | stdout 自动 UTF-8 解码成字符串 |
快速开始:
npm install execa
import { execa } from 'execa'
const { stdout } = await execa('echo',['hello world'])
console.log(stdout);
try{
await execa('unknown-command');
} catch(error){
console.log(error.shortMessage);
console.log(error.exitCode);
}
await 返回 {stdout, stderr},正确回调的返回结果和错误结果。
核心API
execa('command',[argument],[options])
最常用形式,数组传参,不需要拼字符串 ,天然规避 shell 注入:
await execa('git', ['log', '--oneline', '-5']);
await execa('echo', ['$HOME'], { shell: true });
$模板字符串命令
虽然 execa 全程只认数组参数,但提供 $ 语法糖让你写模板字符串,同时自动做参数转义 :
import { $ } from 'execa';
await $`echo hello`;
await $`git commit -m ${'feat: 新功能'}`;
const { stdout } = await $`ls -al`;
execaSync('command',[arguments],[options])
同步版本,适合脚本初始化和简单场景:
import { execaSync } from 'execa';
const { stdout } = execaSync('npm', ['--version']);
console.log(stdout);
结果对象
await 后返回的对象包含:
- stdout — 标准输出字符串
- stderr — 错误输出字符串
- exitCode — 退出码
- failed — 布尔值,是否失败
- command — 实际执行的完整命令(便于日志)
- timedOut、killed、isCanceled 等状态标记
execa 的异常对象非常全(继承 Error):
- error.stdout / error.stderr — 失败时的输出/错误输出
- error.exitCode
- error.message — 命令+args+退出码
- error.shortMessage — 精简错误信息
- error.failed — true
- error.timedOut / error.isCanceled / error.killed — 各种失败原因标记
- error.command — 实际的完整命令串
同步execa返回的是result对象
常用配置项(options)
| 选项 | 作用 | 示例 |
|---|---|---|
| cwd | 指定工作目录 | { cwd: '/path/to/project' } |
| env | 注入环境变量(默认继承父进程) | { env: { FOO: 'bar' } } |
| shell | 用 shell 执行(支持管道、重定向、通配符) | { shell: true } |
| timeout | 超时强杀(毫秒) | { timeout: 5000 } |
| input | 给子进程喂 stdin 数据 | { input: 'hello stdin' } |
| stdio | 控制输入输出流向 | { stdio: 'inherit' } 可直接打印到终端 |
| reject | 失败是否抛错,false 时不抛、返回结果对象 | { reject: false } |
| buffering | 关闭输出缓冲、实时流式读取(v7+) | { buffering: false } 配合 stream.pipe()/catch() |
| windowsHide | Windows 下隐藏子进程窗口 | 默认 true |
加载配置与默认值 :
const res = await execa('node', ['app.js'], {
cwd: path.resolve('./server'),
timeout: 10000,
env: { NODE_ENV: ' 'production' },
});
进阶能力
1 管道(stream 链式)
import { execa } from 'execa';
const psResult = await execa('ps', ['aux'], { stripFinalNewline: false });
await execa('grep', ['node'], { input: psResult.stdout });
2 流式实时输出(看到命令一行行打,而不是最后一次性)
const subprocess = execa('npm', ['run', 'build'], { buffering: false });
subprocess.stdout.pipe(process.stdout);
subprocess.stderr.pipe(process.stderr);
subprocess.catch((error) => {
console.error(error.stderr);
});
3 取消任务(kill / cancel)
const subprocess = execa('node', ['long-task.js']);
setTimeout(() => subprocess.kill('SIGTERM'), 3000);
await subprocess;
import { execa } from 'execa';
const controller = new AbortController();
setTimeout(() => controller.abort(), 3000);
await execa('node', ['long-task.js'], { signal: controller.signal });
4 超时
try {
await execa('ping', ['-t', '127.0.0.1'], { timeout: 2000 });
} catch (error) {
console.log(error.timedOut);
}
5 拒绝策略(reject: false)
不想让失败抛异常、只想拿退出码判断时:
const result = await execa('my-tool', args, { reject: false });
console.log(result.exitCode, result.failed, result.stderr);
6 执行 shell 整段脚本
const { stdout } = await execa('bash', ['-c', 'echo a && echo b']);