Node.js 本身运行在 JavaScript 引擎上(如 V8),它本身不直接与操作系统交互,Node.js 提供了强大的内置模块(如 child_process)来创建子进程,这些子进程可以执行外部程序,比如我们熟悉的 Linux Shell 命令。

下面我将详细介绍几种主要的方法,从最常用到更现代的方案。
核心方法:child_process 模块
这是 Node.js 官方提供、功能最全面、也是最基础的方法,它提供了多种不同的方式来调用命令,以适应不同的同步/异步需求。
child_process.exec() - 最常用,功能强大
exec() 适合执行命令并希望获取其完整的输出(包括 stdout 和 stderr),它会启动一个 shell(在 Linux 上通常是 /bin/sh)来执行命令,因此可以使用 shell 的特性,如管道 ()、重定向 (>)、通配符 () 等。
特点:

- 异步:默认是异步的,不会阻塞 Node.js 的事件循环。
- 缓冲输出:它会将命令的输出缓存在内存中,直到命令执行完毕,如果命令输出非常大,可能会消耗大量内存。
- 回调函数:通过回调函数来获取输出和错误信息。
示例:
const { exec } = require('child_process');
// 执行一个简单的命令
exec('ls -l', (error, stdout, stderr) => {
if (error) {
console.error(`执行出错: ${error.message}`);
return;
}
if (stderr) {
console.error(`错误输出: ${stderr}`);
return;
}
console.log('命令输出:');
console.log(stdout);
});
// 使用 shell 特性,例如管道
exec('ps aux | grep node', (error, stdout, stderr) => {
if (!error && !stderr) {
console.log('找到的 Node.js 进程:');
console.log(stdout);
}
});
Promise 版本 (更现代的写法):
使用 util.promisify 可以将基于回调的 exec 函数转换为返回 Promise 的函数,这样配合 async/await 语法会更优雅。
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
async function listFiles() {
try {
const { stdout } = await execPromise('ls -l /tmp');
console.log('/tmp 目录下的文件:');
console.log(stdout);
} catch (error) {
console.error(`执行失败: ${error}`);
}
}
listFiles();
child_process.execSync() - 同步执行
如果你需要在代码中同步地执行一个命令,并等待它完成后才能继续执行后续代码,可以使用 execSync()。

特点:
- 同步:会阻塞 Node.js 的事件循环,直到命令执行完毕。
- 直接返回:直接返回命令的
stdout输出。 - 抛出异常:如果命令执行失败(返回非零退出码),它会直接抛出一个
Error对象,而不是通过回调或 Promise 返回。
示例:
const { execSync } = require('child_process');
try {
// 同步获取当前工作目录
const currentDir = execSync('pwd').toString();
console.log('当前工作目录是:', currentDir.trim());
// 同步创建一个目录
console.log('正在创建目录 my-test-dir...');
execSync('mkdir my-test-dir');
console.log('目录创建成功!');
} catch (error) {
console.error(`执行同步命令失败: ${error.message}`);
// 如果需要,可以访问 error.status 获取退出码
console.error('退出码:', error.status);
}
⚠️ 警告:在 Web 服务器或高并发应用中谨慎使用同步方法,因为它会阻塞整个进程,严重影响性能。
child_process.spawn() - 最灵活,适合大量数据流
spawn() 是最底层的接口,它启动一个新进程来执行命令,与 exec() 不同,它不会将输出缓存在内存中,而是通过流(stdout 和 stderr 是 Stream 对象)来处理数据。
特点:
- 异步:默认是异步的。
- 流式处理:非常适合处理命令会产生大量输出的场景(如
tar解压、ffmpeg视频处理),因为它可以边处理边输出,不会耗尽内存。 - 更少的 shell 开销:默认不启动 shell,因此比
exec稍微高效一点,如果需要 shell 特性,需要将其作为第一个参数传入数组。
示例:
const { spawn } = require('child_process');
// 启动一个进程来执行 'ls -l /'
const ls = spawn('ls', ['-l', '/']); // 不使用 shell
// 监听标准输出流
ls.stdout.on('data', (data) => {
console.log(`标准输出:\n${data}`);
});
// 监听标准错误流
ls.stderr.on('data', (data) => {
console.error(`标准错误:\n${data}`);
});
// 监听进程结束事件
ls.on('close', (code) => {
console.log(`子进程退出,退出码: ${code}`);
});
child_process.execFile() - 比 spawn 更高效
execFile() 与 spawn() 类似,但它直接执行指定的可执行文件,而不先启动 shell,这比 spawn 或 exec 更高效,因为不需要先解析 shell 命令。
特点:
- 异步:默认是异步的。
- 不启动 shell:直接执行文件,更安全、更高效。
- 也支持流式处理:与
spawn一样,stdout和stderr也是流。
示例:
const { execFile } = require('child_process');
// 直接执行 /bin/ls 程序
execFile('/bin/ls', ['-l', '/'], (error, stdout, stderr) => {
if (error) {
throw error;
}
console.log(stdout);
});
总结与如何选择
| 方法 | 类型 | 输出处理 | 适用场景 |
|---|---|---|---|
exec() |
异步 | 缓冲在内存中 | 最常用,需要获取完整输出、使用 shell 特性(管道、重定向)的场景。 |
execSync() |
同步 | 缓冲在内存中 | 脚本任务,需要在脚本中同步执行命令,并立即使用结果。避免在高并发服务中使用。 |
spawn() |
异步 | 流式处理 | 大数据量,处理会产生大量输出的命令(如视频处理、压缩/解压),或需要与子进程进行持续交互。 |
execFile() |
异步 | 流式处理 | 性能要求高,当确定要执行的是一个具体的可执行文件,且不需要 shell 特性时,这是最高效的选择。 |
重要注意事项
-
安全性:防止命令注入 如果用户输入直接拼接到命令字符串中,可能会造成严重的安全漏洞(命令注入)。 ❌ 危险的做法:
const userInput = '; rm -rf / #'; // 恶意输入 exec(`ls -l ${userInput}`); // 这会执行 ls -l 和 rm -rf /✅ 安全的做法:
- 首选
spawn或execFile:它们将命令和参数分开,可以有效防止注入。// 安全的 spawn 用法 const userInput = 'malicious; rm -rf /'; // spawn 会将 userInput 作为 'find' 命令的一个参数,而不是作为 shell 命令的一部分来执行 const find = spawn('find', ['/home', '-name', userInput]); - 如果必须用
exec,请对用户输入进行严格的验证和清理。
- 首选
-
环境变量 子进程会继承父进程(即你的 Node.js 应用)的环境变量,如果你需要为子进程设置特定的环境变量,可以在
spawn或exec的options参数中指定env对象。const { exec } = require('child_process'); exec('my-command', { env: { ...process.env, MY_VAR: 'hello' } }, (error, stdout) => { // ... }); -
工作目录 同样,可以在
options参数中通过cwd(current working directory) 来指定子进程的工作目录。
浏览器环境中的情况
如果你是在浏览器(如 Chrome, Firefox)中运行 JavaScript,你无法直接调用 Linux 命令,浏览器出于安全考虑,运行在一个沙箱环境中,禁止直接访问操作系统。
浏览器中与“命令行”最接近的体验是 WebAssembly (WASM),理论上,你可以将一个用 C/C++/Rust 等语言编写的、实现了命令行工具功能的程序编译成 WASM 模块,然后在 JavaScript 中加载并调用它,但这相当于在浏览器中运行了一个精简版的“Linux 工具”,而不是调用宿主操作系统的真实命令,这是一个非常高级和复杂的用法。
