child_process.spawn() 是 Node.js/Electron 中用于创建子进程的核心方法,它提供了最灵活的子进程控制方式,特别适合处理大量数据或需要流式输入输出的场景。
一、spawn() 的核心功能
- 启动新进程:直接启动指定的可执行文件或命令
- 流式 I/O:通过流(Stream)的方式处理子进程的输入输出
- 精细控制:可以独立控制 stdin、stdout 和 stderr
- 无缓冲处理:不像
exec()那样缓冲整个输出,适合大数据量
二、方法签名
const { spawn } = require('child_process');
const child = spawn(command[, args][, options]);
三、参数详解
1. command (必需)
- 类型:
string - 说明:要执行的命令或可执行文件路径
- 最佳实践:
- 使用绝对路径确保可靠性
- 在 Windows 上注意可执行文件扩展名
2. args (可选)
- 类型:
Array<string> - 默认值:
[] - 特殊用法:
// 安全参数传递(避免注入)
const args = [
'--input',
sanitize(userInput), // 必须对用户输入进行消毒
'--output',
'/safe/path'
];
3. options (可选)
完整配置选项表:
| 选项 | 类型 | 默认值 | 重要说明 |
|---|---|---|---|
cwd |
string | null | 设置子进程工作目录 |
env |
Object | process.env | 定制环境变量 |
argv0 |
string | undefined | 显式设置 argv[0] |
stdio |
Array/string | 'pipe' | 控制 stdio 配置 |
detached |
boolean | false | 使子进程独立 |
uid |
number | - | 设置用户身份 (Unix) |
gid |
number | - | 设置组身份 (Unix) |
shell |
boolean/string | false | 指定 shell 路径 |
windowsHide |
boolean | true | 隐藏 Windows 控制台窗口 |
关键选项详解:
stdio配置:
三种标准流:
stdin(标准输入):流编号 0stdout(标准输出):流编号 1stderr(标准错误):流编号 2
配置形式:
stdio可以配置为:字符串(简写形式); 数组(精细控制每个流)
字符串简写形式:
| 值 | 描述 | 等效数组形式 |
|---|---|---|
'pipe' |
创建管道(默认值) | ['pipe', 'pipe', 'pipe'] |
'ignore' |
忽略子进程的 stdio | ['ignore', 'ignore', 'ignore'] |
'inherit' |
继承父进程的 stdio | [process.stdin, process.stdout, process.stderr] |
- 使用示例:
const { spawn } = require('child_process');
// 继承父进程的 stdio(子进程输出会显示在父进程控制台)
spawn('node', ['child.js'], { stdio: 'inherit' });
// 完全忽略子进程的 stdio
spawn('node', ['child.js'], { stdio: 'ignore' });
// 创建管道(默认行为)
spawn('node', ['child.js'], { stdio: 'pipe' });
数组精细配置:
数组形式可以分别配置 stdin、stdout 和 stderr,甚至可以配置额外的文件描述符。
- 每个位置可接受的值类型
| 值类型 | 描述 | 示例 |
|---|---|---|
'pipe' |
创建管道 | 'pipe' |
'ignore' |
忽略该流 | 'ignore' |
'inherit' |
继承父进程对应流 | 'inherit' |
| Stream 对象 | 使用现有的流 | process.stderr |
| 文件描述符 | 使用现有文件描述符 | fs.openSync('log.txt', 'w') |
| null/undefined | 使用默认值('pipe') | null |
- 基础配置示例
// 分别配置三个标准流
spawn('node', ['child.js'], {
stdio: [
'pipe', // stdin (0)
'ignore', // stdout (1)
process.stderr // stderr (2) 继承父进程的 stderr
]
});
- 高级文件重定向示例
const fs = require('fs');
const { spawn } = require('child_process');
// 将 stdout 和 stderr 重定向到文件
const out = fs.openSync('./out.log', 'a');
const err = fs.openSync('./err.log', 'a');
const child = spawn('node', ['child.js'], {
stdio: [
'pipe', // stdin
out, // stdout 写入文件
err // stderr 写入文件
]
});
- 额外文件描述符(fd 3+)
数组长度可以超过 3,配置额外的文件描述符:
const fs = require('fs');
const { spawn } = require('child_process');
// 创建额外的 IPC 通道
const child = spawn('node', ['child.js'], {
stdio: ['pipe', 'pipe', 'pipe', 'pipe'] // fd 3 用于自定义通信
});
// 子进程可以通过 process.stdio[3] 访问这个通道
child.stdio[3].write('Hello from parent');
shell选项:
false:直接执行命令(推荐安全用法)true:使用系统默认 shell- 字符串:指定 shell 路径
- 安全警告:启用 shell 可能导致命令注入漏洞
四、使用注意事项
1. 安全最佳实践
- 永远不要直接拼接用户输入:
// 危险!可能被注入
spawn(`convert ${userInput} output.jpg`, { shell: true });
// 安全做法
spawn('convert', [
sanitize(userInput), // 必须消毒
'output.jpg'
], { shell: false });
- 文件路径处理:
const { resolve } = require('path');
// 不安全
spawn('ls', [userProvidedPath]);
// 安全
const safePath = resolve(BASE_DIR, normalize(userProvidedPath));
spawn('ls', [safePath]);
2. 资源管理
- 进程泄漏防护:
const processes = new Set();
function safeSpawn(...args) {
const child = spawn(...args);
processes.add(child);
child.on('exit', () => {
processes.delete(child);
});
return child;
}
// 应用退出时清理
app.on('before-quit', () => {
processes.forEach(child => child.kill());
});
- 流处理必做事项:
const child = spawn('long-running-process');
// 必须处理 error 事件防止内存泄漏
child.stdout.on('data', () => { /*...*/ });
child.stdout.on('error', (err) => {
console.error('Stdout error:', err);
});
// 不使用的流必须销毁
child.stdin.end();
3. 跨平台兼容性
- 路径分隔符处理:
const path = require('path');
const cmd = process.platform === 'win32'
? 'cmd.exe'
: 'bash';
const args = process.platform === 'win32'
? ['/c', 'dir']
: ['-c', 'ls'];
- 可执行文件扩展名:
function getPlatformExecutable(name) {
if (process.platform === 'win32') {
return `${name}.exe`;
}
return name;
}
spawn(getPlatformExecutable('myapp'));
五、完整用法示例
基础示例:列出目录内容
const { spawn } = require('child_process');
// Unix-like、OHOS系统
const ls = spawn('ls', ['-lh', '/usr']);
// Windows 系统
// const dir = spawn('cmd.exe', ['/c', 'dir', 'C:\\']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`子进程退出,退出码 ${code}`);
});
六、高级配置技巧
1. 使用 shell 语法
// 启用 shell 语法(如管道、重定向等)
const child = spawn('find . -type f | wc -l', {
shell: true,
stdio: 'inherit' // 继承父进程的 stdio
});
2. 分离长时间运行的进程
const child = spawn('node', ['server.js'], {
detached: true,
stdio: 'ignore'
});
// 允许父进程退出而子进程继续运行
child.unref();
3. 环境变量定制
const child = spawn('echo', ['$ENV_VAR'], {
env: { ...process.env, ENV_VAR: 'custom value' }
});
七、electron上验证测试方法
端到端测试 (Electron 环境)
- main.js
const { app, BrowserWindow, ipcMain, Tray, nativeImage } = require('electron');
const { spawn } = require('child_process');
const path = require('path');
let mainWindow, tray;
function createWindow() {
tray = new Tray(nativeImage.createFromPath(path.join(__dirname, 'electron_white.png')));
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
console.log('electron-demo start');
mainWindow.loadFile('index.html');
}
app.whenReady().then(() => {
createWindow();
app.on('activate',() => {
if(BrowserWindow.getAllWindow().length === 0) createWindow();
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
});
// 监听渲染进程的触发请求
ipcMain.on('run-script', (event, arg) => {
// 示例:运行一个简单的 ping 命令(跨平台)
const child = spawn('ping', ['-c', '4', 'www.baidu.com']);
// 监听子进程输出
child.stdout.on('data', (data) => {
// 将数据发送到渲染进程
mainWindow.webContents.send('script-output', data.toString());
});
child.stderr.on('data', (data) => {
mainWindow.webContents.send('script-error', data.toString());
});
child.on('close', (code) => {
mainWindow.webContents.send('script-output', `子进程退出,退出码: ${code}`);
});
});
- preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
runScript: () => ipcRenderer.send('run-script'),
onScriptOutput: (callback) => ipcRenderer.on('script-output', (_event, value) => callback(value)),
onScriptError: (callback) => ipcRenderer.on('script-error', (_event, value) => callback(value))
});
- renderer.js
// renderer.js
const startBtn = document.getElementById('start');
const output = document.getElementById('output');
startBtn.addEventListener('click', () => {
window.electronAPI.runScript();
});
// 接收主进程消息
window.electronAPI.onScriptOutput((data) => {
output.innerText += data;
});
window.electronAPI.onScriptError((data) => {
output.innerText += `错误: ${data}`;
});
- index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron Child Process Demo</title>
</head>
<body>
<h1>Electron Child Process Demo</h1>
<p>查看控制台输出以了解主进程和子进程的通信情况。</p>
<button id="start">运行脚本</button>
<pre id="output"></pre>
<script src="renderer.js"></script>
</body>
</html>
- package.json
{
"name": "electron-example",
"version": "1.0.0",
"description": "Electron simple example",
"main": "main.js",
"scripts": {
"start": "electron ."
}
}
- 运行效果
-
启动后界面展示如下

-
点击运行脚本按钮后

-
完整代码
八、常见问题解决方案
-
命令找不到错误
- 确保命令在系统 PATH 中
- 或使用绝对路径
- 或在选项中指定
shell: true
-
跨平台兼容性问题
const cmd = process.platform === 'win32' ? 'dir' : 'ls'; const args = process.platform === 'win32' ? ['/w'] : ['-lh']; const child = spawn(cmd, args); -
进程不退出问题
- 确保正确处理了所有流
- 必要时调用
child.kill()
-
性能优化
- 对大输出使用流处理
- 避免频繁创建/销毁进程,考虑进程池
九、与 Electron 集成的注意事项
-
渲染进程中使用
// 在渲染进程中 const { ipcRenderer } = require('electron'); ipcRenderer.invoke('spawn-command', 'ls', ['-lh']) .then(output => console.log(output)); // 在主进程中处理 ipcMain.handle('spawn-command', async (event, cmd, args) => { return new Promise((resolve, reject) => { const child = spawn(cmd, args); let output = ''; child.stdout.on('data', (data) => output += data.toString()); child.on('close', () => resolve(output)); child.on('error', reject); }); }); -
安全考虑
- 永远不要直接将用户输入传递给
spawn() - 使用白名单验证命令和参数
- 考虑使用
execFile代替,除非需要 shell 特性
- 永远不要直接将用户输入传递给
-
打包后路径问题
- 使用
app.getPath()获取正确路径 - 对于打包的二进制文件,使用
extraResources配置
- 使用
const { app } = require('electron');
const path = require('path');
function getExecutablePath(name) {
if (app.isPackaged) {
// 打包后的路径结构
return process.platform === 'win32'
? path.join(process.resourcesPath, 'bin', `${name}.exe`)
: path.join(process.resourcesPath, 'bin', name);
}
// 开发环境路径
return path.join(__dirname, 'bin', name);
}
// 使用示例
const child = spawn(getExecutablePath('my-tool'), ['--arg=value']);
通过以上详细介绍和示例,你应该能够全面掌握 Electron 中 child_process.spawn() 的使用方法,并能在实际项目中灵活应用。