- 开发工具
【免费下载链接】execa
Process execution for humans
本指南以 execa 官方 README(readme.md)为核心骨架,结合仓库源码与测试展开。Execa 运行你脚本、应用或库中的命令,与 shell 不同,它专门为编程化使用(programmatic usage)而优化,构建于 Node.js 核心模块
child_process之上。读完本文,你将掌握 execa 的模板字符串语法、$脚本接口、本地二进制执行、多进程管道、输入输出类型转换、IPC 消息通信、优雅终止以及调试与自定义日志等完整实战能力。
一、项目定位:为人类而生的进程执行
Execa 的口号是"Process execution for humans"。它把 Node.js 底层child_process繁琐的spawn/exec/fork调用包装成简洁、类型安全、Promise 化的高层 API,并针对程序化调用场景做了大量优化,这一点在 docs/bash.md 中有专门对比说明。
从 package.json 可以看到当前仓库版本为10.0.1,要求Node.js >= 22,采用 ESM("type": "module")规范,通过exports字段暴露types(index.d.ts)与default(index.js)入口,内置依赖包括get-stream、npm-run-path、signal-exit、strip-final-newline、yoctocolors等,这些依赖分别服务于流式收集输出、本地二进制路径解析、退出清理、去换行与彩色输出等底层能力。
二、安装
npm install execa安装后即可在 ESM 项目中直接导入:
import {execa} from 'execa';三、核心特性总览
Execa 的 Features 清单,既是能力地图,也是本文后续各节的索引:
- 简单语法:Promise + 模板字符串,类似
zx的体验。 - 脚本接口:
$命令,提供更贴近 shell 的书写方式。 - 免转义免引号:无需 escaping 与 quoting,从设计上杜绝 shell 注入风险(详见 docs/escaping.md)。
- 本地二进制:无需
npx即可执行项目本地安装的 CLI 工具。 - 增强的 Windows 支持:正确处理 shebang、
PATHEXT、优雅终止等(详见 docs/windows.md)。 - 详细错误、verbose 模式与自定义日志:服务于调试(详见 docs/debugging.md)。
- 多子进程管道:可获取中间结果、支持多源/多目标与 unpipe(详见 docs/pipe.md)。
- 输出切分/迭代:按文本行切分或渐进迭代。
- 去除多余换行(详见 docs/lines.md)。
- 任意输入类型:文件、字符串、
Uint8Array、迭代器、对象乃至几乎任意其他类型(分别参见 docs/input.md、docs/binary.md、docs/streams.md、docs/transform.md)。 - 任意输出类型:或将输出重定向到文件。
- 交错输出:
stdout与stderr按终端真实打印顺序交错合并。 - 编程式与终端输出并存:一边在代码中获取结果,一边打印到控制台。
- 输入输出变换/过滤:用简单函数即可实现(docs/transform.md)。
- Node.js 流与 Web 流互操作,或将子进程转换为流(docs/streams.md)。
- 子进程消息通信(docs/ipc.md)。
- 保证子进程退出:即使它拦截了终止信号,或当前进程意外结束(docs/termination.md)。
四、文档地图
Execa 的官方文档按主题拆分为独立章节,是深入学习每个特性的权威入口:
执行类:基本执行、转义/引号、Shell、脚本、Node.js 文件、环境、错误、终止。
输入输出类:输入、输出、文本行、二进制数据、变换。
高级用法:多子进程管道、流、进程间通信、调试、Windows、与 Bash/zx 的差异、精简包、TypeScript、API 参考。
五、执行示例
5.1 简单语法:模板字符串
execa可直接以标签模板字符串调用,命令无需引号包裹,参数插值也无需手动转义:
import {execa} from 'execa'; const {stdout} = await execa`npm run build`; // 打印命令输出 console.log(stdout);从源码看,这一语法糖由 lib/methods/template.js 中的parseTemplates()实现:它会判断传入参数是否带raw属性的模板数组(isTemplateString),将其解析为[file, commandArguments, {}]形式,再交给统一核心执行。模板表达式${expression}支持字符串与数字,也支持直接插入上一个子进程的结果对象(如${subprocess}的stdout);若误插入未await的 Promise 或ChildProcess,会抛出TypeError提示"请使用 ${await subprocess} 而不是 ${subprocess}"。
5.2 脚本接口:$
$接口与execa等价,但预置了脚本友好的默认选项。按 lib/methods/script.js 的实现,当未指定input、inputFile与stdio时,$会自动设置stdin: 'inherit',并且preferLocal: true作为深层次选项在管道场景中对两个命令都生效:
import {$} from 'execa'; const {stdout: name} = await $`cat package.json`.pipe`grep name`; console.log(name); const branch = await $`git branch --show-current`; await $`dep deploy --branch=${branch}`; await Promise.all([ $`sleep 1`, $`sleep 2`, $`sleep 3`, ]); const directoryName = 'foo bar'; await $`mkdir /tmp/${directoryName}`;注意最后一行:目录名含空格,但模板插值让 execa 将其作为单个参数传递,无需任何引号或转义。$还提供同步变体$.sync与别名$.s(由setScriptSync挂载)。
5.3 本地二进制:无需 npx
安装项目本地依赖后,用preferLocal: true选项即可直接执行,无需npx前缀:
$ npm install -D eslintawait execa({preferLocal: true})`eslint`;底层由npm-run-path库(见 lib/arguments/options.js 的getEnv())在preferLocal或node: true时自动拼接node_modules/.bin到 PATH 环境变量中,从环境层面实现"本地优先"解析。
5.4 管道多个子进程
管道返回的 Promise 会解析为目标子进程的结果,同时通过pipedFrom保留每一级中间结果:
const {stdout, pipedFrom} = await execa`npm run build` .pipe`sort` .pipe`head -n 2`; // 相当于 `npm run build | sort | head -n 2` 的输出 console.log(stdout); // 相当于 `npm run build | sort` 的输出 console.log(pipedFrom[0].stdout); // 相当于 `npm run build` 的输出 console.log(pipedFrom[0].pipedFrom[0].stdout);管道能力由 lib/pipe/setup.js 的pipeToSubprocess()驱动:它把源子进程的stdout/stderr/stdio接到目标子进程的stdin,并Promise.race两个子进程的完成与 unpipe 中止信号;.pipe()返回值还会转发目标的stdio、all、可迭代方法以及 IPC 方法(sendMessage、getOneMessage、getEachMessage)。对比 shell 管道,execa 的优势是能拿到中间结果、支持一个源接多个目标、多个源接一个目标,以及按需 unpipe(详见 docs/pipe.md)。
六、输入输出示例
6.1 交错输出(all)
all: true时,返回结果的all字段包含stdout与stderr按实际打印顺序交错合并的内容:
const {all} = await execa({all: true})`npm run build`; // stdout + stderr 交错 console.log(all);该流由 lib/resolve/all-async.js 的makeAllStream()创建,同时收集两个通道并按时间顺序合并。
6.2 编程式输出 + 终端输出
stdout选项设为['pipe', 'inherit']时,输出既被 execa 捕获供程序读取,又同时打印到终端:
const {stdout} = await execa({stdout: ['pipe', 'inherit']})`npm run build`; // stdout 也会打印到终端 console.log(stdout);inherit意味着子进程直接复用父进程的标准流(参见 docs/output.md 与 stdio 选项文档 docs/api.md)。
6.3 简单输入
const getInputString = () => { /* ... */ }; const {stdout} = await execa({input: getInputString()})`sort`; console.log(stdout);字符串输入会写入子进程的stdin并在结束后关闭(详见 docs/input.md)。
6.4 文件输入 / 文件输出
// 类似: npm run build < input.txt await execa({stdin: {file: 'input.txt'}})`npm run build`; // 类似: npm run build > output.txt await execa({stdout: {file: 'output.txt'}})`npm run build`;stdin/stdout 支持{file: path}形式的文件描述对象,由 lib/stdio/stdio-option.js 及 stdio 处理器展开为文件重定向。
6.5 按文本行切分
const {stdout} = await execa({lines: true})`npm run build`; // 打印前 10 行 console.log(stdout.slice(0, 10).join('\n'));lines: true时stdout从字符串变为字符串数组,并自动去除行尾换行符。从 lib/arguments/options.js 看,lines生效还需满足编码为非二进制且buffer开启两个前提条件。相关实现见 lib/io/strip-newline.js 与 lib/io/iterate.js。
七、流式处理示例
7.1 逐行迭代
子进程本身可直接被for await...of迭代,逐行处理输出:
for await (const line of execa`npm run build`) { if (line.includes('WARN')) { console.warn(line); } }迭代由 lib/convert/iterable.js 的createIterable()实现,返回一个Symbol.asyncIterator,按行产出文本;相关测试见 test/io/iterate.js 与 test/stdio/iterable.js。
7.2 转换/过滤输出
stdout选项可接收一个生成器函数,对每一行做变换或过滤:
let count = 0; // 先过滤掉包含 secret 的行,再为每行加上行号前缀 const transform = function * (line) { if (!line.includes('secret')) { yield `[${count++}] ${line}`; } }; await execa({stdout: transform})`npm run build`;变换管线的核心位于 lib/transform/run-async.js、lib/transform/run-sync.js 与 lib/transform/split.js:生成器按文本行切分输入,逐个yield转换后的行写回目标流。完整能力参见 docs/transform.md。
7.3 Web 流
直接传入fetch返回的ReadableStream作为stdin:
const response = await fetch('https://example.com'); await execa({stdin: response.body})`sort`;Web 流支持由 lib/convert/web.js 提供,可把 Web 流、Node 流与子进程的 stdio 统一起来。
7.4 转换为 Duplex 流
子进程可通过.duplex()转换为双工流,与node:stream/promises的pipeline无缝衔接:
import {execa} from 'execa'; import {pipeline} from 'node:stream/promises'; import {createReadStream, createWriteStream} from 'node:fs'; await pipeline( createReadStream('./input.txt'), execa`node ./transform.js`.duplex(), createWriteStream('./output.txt'), );duplex()由 lib/convert/duplex.js 实现,本质是把子进程stdin作为可写端、stdout作为可读端封装成PassThrough类双工流;仓库还提供readable()、writable()、readableStream()、writableStream()、transformStream()等转换方法(见 lib/convert/add.js)。
八、进程间通信(IPC)
8.1 交换消息
父进程与子进程可通过 promise 化的 API 双向发消息(子进程侧用execaNode启动以保证 IPC 通道开启):
// parent.js import {execaNode} from 'execa'; const subprocess = execaNode`child.js`; await subprocess.sendMessage('Hello from parent'); const message = await subprocess.getOneMessage(); console.log(message); // 'Hello from child'// child.js import {getOneMessage, sendMessage} from 'execa'; const message = await getOneMessage(); // 'Hello from parent' const newMessage = message.replace('parent', 'child'); // 'Hello from child' await sendMessage(newMessage);IPC 实现分两条链路:父进程侧由 lib/ipc/methods.js 的addIpcMethods()把sendMessage/getOneMessage/getEachMessage挂到子进程对象上;子进程侧通过getIpcExport()导出同名函数。消息发送在 lib/ipc/send.js,单条读取在 lib/ipc/get-one.js,逐条迭代在 lib/ipc/get-each.js。
8.2 任意输入类型(ipcInput)
ipcInput允许传入包含正则、Set等复杂结构的对象数组作为子进程输入,且自动开启ipc:
// main.js import {execaNode} from 'execa'; const ipcInput = [ {task: 'lint', ignore: /test\.js/}, {task: 'copy', files: new Set(['main.js', 'index.js']), }]; await execaNode({ipcInput})`build.js`;// build.js import {getOneMessage} from 'execa'; const ipcInput = await getOneMessage();从 lib/arguments/options.js 的addDefaultOptions()可以看到默认ipc = ipcInput !== undefined || gracefulCancel,即一旦传入ipcInput或启用优雅取消,IPC 通道自动打开;消息序列化默认采用serialization: 'advanced',这也是能传输RegExp、Set等类型的原因(lib/ipc/ipc-input.js 负责校验)。
8.3 任意输出类型(ipcOutput)
子进程侧多次sendMessage的结构化消息会按序汇入父进程结果的ipcOutput数组:
// main.js import {execaNode} from 'execa'; const {ipcOutput} = await execaNode`build.js`; console.log(ipcOutput[0]); // {kind: 'start', timestamp: date} console.log(ipcOutput[1]); // {kind: 'stop', timestamp: date}// build.js import {sendMessage} from 'execa'; const runBuild = () => { /* ... */ }; await sendMessage({kind: 'start', timestamp: new Date()}); await runBuild(); await sendMessage({kind: 'stop', timestamp: new Date()});8.4 优雅终止(gracefulCancel)
通过AbortController与gracefulCancel: true组合,可在取消时让子进程"优雅收尾"——先通知子进程自行清理,而非立刻强杀:
// main.js import {execaNode} from 'execa'; const controller = new AbortController(); setTimeout(() => { controller.abort(); }, 5000); await execaNode({ cancelSignal: controller.signal, gracefulCancel: true, })`build.js`;// build.js import {getCancelSignal} from 'execa'; const cancelSignal = await getCancelSignal(); const url = 'https://example.com/build/info'; const response = await fetch(url, {signal: cancelSignal});取消相关的校验与执行逻辑在 lib/terminate/cancel.js(校验cancelSignal必须是AbortSignal,并在中止时触发kill())与 lib/terminate/graceful.js(子进程侧通过getCancelSignal()拿到取消信号,见 lib/ipc/graceful.js)。完整终止语义参见 docs/termination.md。
九、调试与日志
9.1 详细错误对象
命令失败时抛出的ExecaError(同步版为ExecaSyncError,定义于 lib/return/final-error.js)携带极其丰富的诊断字段:
import {execa, ExecaError} from 'execa'; try { await execa`unknown command`; } catch (error) { if (error instanceof ExecaError) { console.log(error); } /* ExecaError: Command failed with ENOENT: unknown command spawn unknown ENOENT at ... at ... { shortMessage: 'Command failed with ENOENT: unknown command\nspawn unknown ENOENT', originalMessage: 'spawn unknown ENOENT', command: 'unknown command', escapedCommand: 'unknown command', cwd: '/path/to/cwd', durationMs: 28.217566, failed: true, timedOut: false, isCanceled: false, isTerminated: false, isMaxBuffer: false, code: 'ENOENT', stdout: '', stderr: '', stdio: [undefined, '', ''], pipedFrom: [] [cause]: Error: spawn unknown ENOENT at ... at ... { errno: -2, code: 'ENOENT', syscall: 'spawn unknown', path: 'unknown', spawnargs: [ 'command' ] } } */ }错误对象在 lib/return/result.js 的makeError()中组装:包含command/escapedCommand、退出码code、stdout/stderr/stdio快照、是否超时/取消/超缓冲等标志,原始 spawn 错误则作为error.cause保留。完整错误语义见 docs/errors.md。
9.2 Verbose 模式
不附加任何配置,连续运行多个命令时,execa 会自动输出分步、分色的执行日志(命令、时长、退出码等):
await execa`npm run build`; await execa`npm run test`;运行效果示意如下(仓库 media/verbose.png):
verbose 输出的生成与格式控制分布在 lib/verbose/ 目录(start.js、complete.js、output.js、error.js、ipc.js等),相关测试见 test/verbose/。
9.3 自定义日志
verbose选项可传入回调函数,将 execa 的事件流接入任意日志框架(示例使用 Winston):
import {execa as execa_} from 'execa'; import {createLogger, transports} from 'winston'; // 用 Winston 将日志写入文件 const transport = new transports.File({filename: 'logs.txt'}); const logger = createLogger({transports: [transport]}); const LOG_LEVELS = { command: 'info', output: 'verbose', ipc: 'verbose', error: 'error', duration: 'info', }; const execa = execa_({ verbose(verboseLine, {message, ...verboseObject}) { const level = LOG_LEVELS[verboseObject.type]; loggerlevel; }, }); await execa`npm run build`; await execa`npm run test`;注意这里通过execa_(options)预先绑定选项生成新的execa实例——这正是 lib/methods/create.js 中"options binding"能力的体现:当createExeca生成的函数收到一个纯对象作为首个参数时,它会合并选项并返回一个绑定了这些默认选项的嵌套版本。verbose回调的type字段涵盖command、output、ipc、error、duration等事件类型,可据此映射日志级别(参见 lib/verbose/custom.js)。
十、与其他方案的区别
Execa 被定位为编程化进程执行工具,与交互式 shell 脚本有本质区别,核心差异包括(详见 docs/bash.md):
- 基于 Promise:所有命令都可
await、可并行、可组合,天然融入异步代码流。 - 模板字符串代替拼接:参数以数组形式传递,空格、特殊字符无需引号,消除注入面。
- 结构化结果与错误:返回包含
stdout/stderr/exitCode/durationMs的对象,错误也是可编程检查的结构。 - 本地二进制优先:自动解析
node_modules/.bin,省去npx。 - 跨平台一致:对 Windows 的 shebang、
PATHEXT做了兼容处理(docs/windows.md)。 - 类型安全:完整的 TypeScript 类型定义见 types/ 与 test-d/(类型级测试),保证编译期发现错误用法。
十一、小结
Execa 用统一的 Promise API 覆盖了进程执行的全场景:从最基础的execa/execaSync/execaNode/$四种入口(导出定义见 index.js),到模板字符串解析(lib/methods/template.js)、选项归一化与默认值(lib/arguments/options.js)、异步核心执行(lib/methods/main-async.js)、流式转换(lib/transform/)、管道编排(lib/pipe/)、IPC 消息(lib/ipc/)与终止控制(lib/terminate/),再配合详细的错误对象与可定制日志,让你以接近 shell 的简洁、远超 shell 的可靠性完成进程编排。深入每个主题时,官方文档(见上文"文档地图")与仓库内 test/ 目录下的对应测试用例都是最佳参考资料。
- 开发工具
【免费下载链接】execa
Process execution for humans
相关推荐
Execa 基础执行完全指南:数组语法、模板字符串语法与返回值详解
Execa 基础执行完全指南:数组语法、模板字符串语法与返回值详解 Execa(Process execution for humans)是构建在 Node.j
开发工具基于 PHP 可变函数与字符串转义的远程命令执行与 WAF 绕过实战指南(webshell 仓库配套)
基于 PHP 可变函数与字符串转义的远程命令执行与 WAF 绕过实战指南(webshell 仓库配套) 本篇技术指南以仓库文档 How To Exploit P
网络安全渗透测试TypeScript 模板字符串(Template Literals)实战指南:插值、多行与标签模板
TypeScript 模板字符串(Template Literals)实战指南:插值、多行与标签模板 导读 模板字符串(Template Literals,又称
教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考