Node.js 主线程是单线程,只能执行 JS 代码,无法直接调用系统终端命令(ls、pnpm create 等)。想要在代码里自动执行命令、实现类似 Cursor 编辑器内置终端、AI Agent 自动化脚手架功能,必须借助内置模块 node:child_process 的 spawn 创建独立子进程。

本文基于一套可完整运行的实践代码,逐行拆解语法、配置、事件、错误处理,附带大量总结区分点,适合反复复习记忆。
javascript
运行
// node 主进程 agent 执行js 单线程// 调用工具去执行命令行任务 (分离出去,独立的子进程) // node 多进程架构 // child_process 做完后, IPC (进程间通信)告诉主进程 // 子进程 import {spawn //启动一个子进程} from 'node:child_process';// mini cursorI/O, 命令行 agent自动化工具const command = 'ls -la';// Linux查看目录命令const command1 = 'pnpm create react-todo-app --template vite'; // 创建Vite+React项目// split分割字符串 + 数组解构剩余运算符,拆分程序名与参数const [cmd, ...args] = command.split(' ');const cwd = process.cwd(); // 获取主进程当前工作目录console.log(cwd);// 创建bash子进程执行shell命令const client = spawn(cmd, args, {cwd, // 指定子进程工作目录,和父进程保持一致stdio: 'inherit', // 子进程继承父进程控制台,日志直接输出终端shell: true // 开启shell解释器,解析完整终端指令});// 缓存子进程创建失败的错误信息let errorMsg = '';// 监听:创建子进程失败事件client.on('error', (err) => {errorMsg = err.message});// 监听:子进程完全关闭销毁事件client.on('close', (code) => {if (code === 0) { // code=0:命令执行成功,正常退出主进程process.exit(0);} else {// 执行失败,打印启动阶段错误if (errorMsg) {console.error(`错误:${errorMsg}`);}// 使用子进程错误码退出,兜底通用错误码1process.exit(code || 1);}})
spawn 创建操作系统独立子进程(bash/GitBash),父子进程内存隔离,依靠 IO 流完成IPC 进程间通信,子进程输出 / 错误传回主进程。javascript
运行
import { spawn } from 'node:child_process';
node:child_process:Node 官方内置进程模块,无需额外安装;spawn:底层创建子进程 API,流式输出,适合长时间运行、持续打印日志的命令(pnpm 安装、vite 启动);exec 适合短命令,会缓存全部输出,大输出易内存溢出;spawn 流式更适合自动化工具。javascript
运行
const command = 'ls -la';const command1 = 'pnpm create react-todo-app --template vite';
ls -la:列出当前目录所有文件,包含隐藏文件、权限、大小、修改时间;command1:模拟 AI 自动化创建前端项目,长参数命令,依赖 shell 解析。javascript
运行
const [cmd, ...args] = command.split(' ');
split(' ') 字符串方法作用:以空格为分隔符,将命令字符串切割为数组示例:'ls -la'.split(' ') → ["ls", "-la"]缺点:多连续空格会产生空字符串,简易脚本可用,复杂 CLI 需优化。
[cmd, ...args] 解构语法cmd:接收数组第一项,代表程序名称(ls/pnpm);...args:剩余运算符,收集数组剩下所有元素,打包为参数数组 ["-la"];适配 spawn(程序名, 参数数组, 配置) 的标准传参格式。javascript
运行
const cwd = process.cwd();console.log(cwd);
process:Node 全局对象,代表当前主线程;process.cwd():全称 current working directory,返回脚本启动时所在文件夹绝对路径;cd 切换目录,不会改变主线程 cwd,父子进程环境完全隔离。javascript
运行
const client = spawn(cmd, args, {cwd,stdio: 'inherit',shell: true});
client:spawn 返回 ChildProcess 子进程实例,用于监听事件、管控子进程生命周期。
cwd传给 spawn,指定子进程运行目录,统一父子进程操作路径。
stdio: 'inherit'stdout.on('data'),命令打印的日志、安装进度、报错直接实时显示在终端,和手动敲命令体验一致;stdio: 'pipe':输出隔离,必须手动监听流才能打印日志。shell: truels -la、管道、多命令拼接等语法;shell:false:spawn 直接查找二进制程序,无法解析带空格的完整命令,只能拆分参数数组传入。javascript
运行
let errorMsg = '';
error 事件与 close 事件是两个独立回调,需要全局变量跨事件存储子进程创建失败的报错文本,最后统一打印。
javascript
运行
client.on('error', (err) => {errorMsg = err.message});
事件名 'error':内置固定事件,仅无法创建子进程时触发;
触发场景:命令不存在(lss)、权限不足、系统资源耗尽;
err:Node 自动生成的 Error 错误实例,自带两大核心属性:
err.message:人类可读报错文本(用于打印展示);err.code:机器识别错误码(如 ENOENT = 找不到程序);关键区分:子进程启动成功、运行中途报错,不会触发 error 事件。
javascript
运行
client.on('close', (code) => {if (code === 0) {process.exit(0);} else {if (errorMsg) {console.error(`错误:${errorMsg}`);}process.exit(code || 1);}})
'close':内置事件,只要子进程成功启动过,无论成功 / 崩溃,销毁时一定会触发;
参数 code:系统自动生成的数字退出码,行业统一规范:
code === 0:执行成功,无异常;process.exit(数字):直接终止 Node 主线程,数字作为脚本执行结果标识,供上层工具识别成败;
code || 1 短路运算:如果 code 为空 /undefined,兜底使用通用错误码 1,保证脚本一定返回失败标识;
逻辑分支拆解:
process.exit(0);表格
| 对比项 | error 事件 | close 事件 |
|---|---|---|
| 触发时机 | 操作系统无法创建子进程 | 子进程成功启动,最终销毁 |
| 触发场景 | 命令不存在、权限不足 | 命令跑完、运行报错、手动终止进程 |
| 回调参数 | err 错误实例(message/code) | code 数字退出码 |
| 是否一定会触发 | 创建失败才触发 | 子进程启动成功就一定会触发 |
| 典型示例 | spawn lss(无此命令) | pnpm 创建项目文件夹已存在 |
只有一开始创建失败才走 error;只要子进程跑起来了,不管成功还是中途报错,最终都会走 close;0 代表成功,非 0 代表失败。
shell:true:启动 shell 翻译器,解析 Linux 命令字符串;stdio:'inherit':日志直接输出控制台,无需手动监听数据流;cwd:统一父子进程工作目录,防止路径错乱。shell:true 和 shell:false 不能混用整串命令,关闭 shell 必须拆分参数数组;stdio:'inherit' 简化代码,但无法捕获输出做二次处理;需要解析输出内容时改用默认 pipe,监听 stdout;error 事件只管「创建子进程失败」,不管命令内部运行错误;code=0 是行业标准成功标识,所有系统命令统一规范;process.cwd() 不受子进程 cd 命令影响,父子进程目录隔离;err.code 机器识别,err.message 给用户看,二者配合使用。如果需要执行第二条命令 command1,只需重复拆分与 spawn 逻辑:
javascript
运行
const [cmd2, ...args2] = command1.split(' ');const client2 = spawn(cmd2, args2, { cwd, stdio: 'inherit', shell: true });
本代码基于 Node.js spawn 创建独立 shell 子进程,模拟 Cursor 编辑器终端自动化 Agent;通过 cwd 统一路径、shell 解析终端指令、stdio 直接输出日志,监听 error 捕获进程创建失败、close 管控子进程生命周期,标准化退出码区分任务成功与失败,实现完整的本地命令自动化工具。