Electron 中的 exec 方法详解
exec 是 Electron 中通过 Node.js 的 child_process 模块提供的一个方法,用于执行系统命令。与 fork 不同,exec 更适合执行 shell 命令而非 Node.js 脚本。
基本概念
child_process.exec() 方法会生成一个 shell 并在该 shell 中运行命令,当完成时将 stdout 和 stderr 传递给回调函数。
基本语法
const { exec } = require('child_process');
exec(command [, options] [, callback])
使用方法
基本示例(ohos electron)
- 具体实现
const { app, BrowserWindow, ipcMain, Tray, nativeImage } = require('electron');
const { exec } = 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,
}
});
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();
});
});
// exec
console.log('electron-demo 即将开始exec!' + __dirname);
const child_echo = exec('echo "test"', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
// ohos electron上ls命令应参照如下实现,否则会有权限问题
const child_ls = exec('ls', { cwd: __dirname, env: { NODE_ENV: 'test' },
stdio: 'pipe' }, (error, stdout, stderr) => {
if (error) {
console.error(`electron-demo 执行错误: ${error}`);
return;
}
console.log(`electron-demo 输出: ${stdout}`);
if (stderr) console.error(`electron-demo 错误: ${stderr}`);
});
// 监听 exit 事件
child_echo.on('exit', (code, signal) => {
console.log(`echo 子进程退出,退出码: ${code}`);
if (signal) {
console.log(`echo 进程被信号终止: ${signal}`);
}
console.log('echo child.exitCode: ', child_echo.exitCode)
});
child_ls.on('exit', (code, signal) => {
console.log(`ls 子进程退出,退出码: ${code}`);
if (signal) {
console.log(`ls 进程被信号终止: ${signal}`);
}
console.log('ls child.exitCode: ', child_ls.exitCode)
});
- 运行效果

使用注意事项
1. 安全性问题
- 永远不要直接执行用户输入:这可能导致命令注入攻击
// 危险示例! const userInput = 'some; rm -rf /'; exec(`ls ${userInput}`, (error, stdout) => { /* ... */ }); // 安全做法:使用参数化或严格验证 const { execFile } = require('child_process'); execFile('ls', [validatedInput], (error, stdout) => { /* ... */ });
2. 性能考虑
exec会启动一个 shell,比execFile开销更大- 对于高频或性能敏感的操作,考虑使用
execFile
3. 跨平台兼容性
- 命令在不同操作系统上可能表现不同
const command = process.platform === 'win32' ? 'dir' : 'ls'; exec(command, (error, stdout) => { /* ... */ });
4. 输出限制
- 默认缓冲区大小为 200KB (1MB 从 Node.js v12.x 开始)
- 大数据量输出可能导致内存问题
// 增加缓冲区大小 exec('ls -lh', { maxBuffer: 1024 * 1024 * 5 }, (error, stdout) => { /* ... */ });
5. 错误处理
- 必须处理 error 和 stderr
- exit code 不为 0 时会触发 error
最佳实践
1. 使用 execFile 替代 exec(当不需要 shell 特性时)
const { execFile } = require('child_process');
// 更安全,不启动 shell
execFile('ls', ['-lh'], (error, stdout, stderr) => {
/* ... */
});
2. 使用 util.promisify 包装为 Promise
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
async function runCommand() {
try {
const { stdout, stderr } = await execAsync('ls -lh');
console.log(stdout);
} catch (error) {
console.error(error);
}
}
3. 设置超时防止挂起
exec('sleep 10', { timeout: 2000 }, (error) => {
if (error?.killed) {
console.log('命令因超时被终止');
}
});
4. 清理子进程
const child = exec('long-running-command');
// 应用退出时清理
process.on('exit', () => {
child.kill();
});
// 或者设置超时
setTimeout(() => {
child.kill();
}, 5000);
5. 工作目录与环境变量
exec('ls -lh', {
cwd: '/path/to/directory', // 设置工作目录
env: { ...process.env, CUSTOM_ENV: 'value' } // 环境变量
}, (error, stdout) => {
/* ... */
});
测试验证方法
1. 基本功能测试
// test-exec.js
const { exec } = require('child_process');
const assert = require('assert');
// 测试正常命令
exec('echo "hello"', (error, stdout, stderr) => {
assert(!error, '不应返回错误');
assert.strictEqual(stdout.trim(), 'hello', '输出应匹配');
assert.strictEqual(stderr, '', '不应有 stderr 输出');
console.log('基本功能测试通过');
});
// 测试错误命令
exec('non-existent-command', (error) => {
assert(error, '应返回错误');
console.log('错误处理测试通过');
});
2. 安全性测试
// 测试命令注入
const maliciousInput = '; echo "hacked" > test.txt';
exec(`echo "safe"${maliciousInput}`, (error, stdout) => {
// 检查是否创建了 test.txt 文件
// 预期结果: 不应创建文件
});
3. 性能测试
// 测试缓冲区限制
const largeOutputCommand = 'yes "This is a long line" | head -n 100000';
console.time('exec');
exec(largeOutputCommand, { maxBuffer: 1024 * 1024 }, (error) => {
console.timeEnd('exec');
if (error?.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
console.log('正确捕获了缓冲区溢出错误');
}
});
4. 超时测试
// timeout-test.js
exec('sleep 5', { timeout: 1000 }, (error) => {
assert(error?.killed, '进程应因超时被终止');
assert(error?.signal === 'SIGTERM', '应收到 SIGTERM 信号');
console.log('超时测试通过');
});
总结
Electron 中的 exec 方法是一个强大的工具,可以执行系统命令并与操作系统交互,但使用时需要注意:
- 安全第一:永远不要直接执行未经验证的用户输入
- 合理使用:根据需求选择
exec或execFile - 完善处理:正确处理错误、超时和子进程生命周期
- 性能考量:注意缓冲区限制和跨平台差异
通过遵循最佳实践和进行充分的测试验证,可以安全有效地在 Electron 应用中使用 exec 方法。