execa
execa 是 NodeJs 官方 child_process 模块的增强替代品。把 JS 里"执行命令"这件麻烦事,做成简单、可靠、对新手友好的 Promise API
对比原生写法,优势立现:
| 操作 | child_process 原生 | execa |
|---|---|---|
| 执行并拿输出 | 要自己包 Promise | 直接 await 返回 {stdout, stderr} |
|---|---|---|
| 捕获错误 | 判断回调 error 对象 | catch 即可,错误对象信息齐全 |
| 二进制输出 | Buffer 处理繁琐 | stdout 自动 UTF-8 解码成字符串 |
快速开始:
shell
npm install execa
js
import { execa } from 'execa'
const { stdout } = await execa('echo',['hello world'])
console.log(stdout); //hello world
try{
await execa('unknown-command');
} catch(error){
console.log(error.shortMessage);
// 命令不存在时:'/bin/sh: unknown-command: command not found'
console.log(error.exitCode); // 127
}
await 返回 {stdout, stderr},正确回调的返回结果和错误结果。
核心API
execa('command',[argument],[options])
最常用形式,数组传参,不需要拼字符串,天然规避 shell 注入:
js
// 执行 git log --oneline -5
await execa('git', ['log', '--oneline', '-5']);
// 传环境变量
await execa('echo', ['$HOME'], { shell: true });
$模板字符串命令
虽然 execa 全程只认数组参数,但提供 $ 语法糖让你写模板字符串,同时自动做参数转义:
js
import { $ } from 'execa';
await $`echo hello`; // 简单命令
await $`git commit -m ${'feat: 新功能'}`; // 参数自动安全转义,防注入
// 同样返回 { stdout, stderr }
const { stdout } = await $`ls -al`;
execaSync('command',[arguments],[options])
同步版本,适合脚本初始化和简单场景:
js
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 |
加载配置与默认值:
js
// cwd 是高频需求,配合 path 使用
const res = await execa('node', ['app.js'], {
cwd: path.resolve('./server'),
timeout: 10000,
env: { NODE_ENV: ' 'production' },
});
进阶能力
1 管道(stream 链式)
js
import { execa } from 'execa';
const psResult = await execa('ps', ['aux'], { stripFinalNewline: false });
await execa('grep', ['node'], { input: psResult.stdout });
2 流式实时输出(看到命令一行行打,而不是最后一次性)
js
const subprocess = execa('npm', ['run', 'build'], { buffering: false });
subprocess.stdout.pipe(process.stdout); // 实时打印
subprocess.stderr.pipe(process.stderr);
// 流式错误处理用 catch 而非 try/catch
subprocess.catch((error) => {
console.error(error.stderr);
});
3 取消任务(kill / cancel)
js
const subprocess = execa('node', ['long-task.js']);
setTimeout(() => subprocess.kill('SIGTERM'), 3000);
await subprocess; // 会被拒绝,错误对象带 killed: true
// v8+ 用 AbortController 更干净
import { execa } from 'execa';
const controller = new AbortController();
setTimeout(() => controller.abort(), 3000);
await execa('node', ['long-task.js'], { signal: controller.signal });
4 超时
js
try {
await execa('ping', ['-t', '127.0.0.1'], { timeout: 2000 });
} catch (error) {
console.log(error.timedOut); // true
}
5 拒绝策略(reject: false)
不想让失败抛异常、只想拿退出码判断时:
js
const result = await execa('my-tool', args, { reject: false });
console.log(result.exitCode, result.failed, result.stderr);
// 不会 throw,一切信息都在 result 里
6 执行 shell 整段脚本
js
const { stdout } = await execa('bash', ['-c', 'echo a && echo b']);