在 node.js 中,可以通过内置的模块 child_process 启动子进程执行程序。例如:
const { exec, spawn } = require('child_process');
// exec
exec('echo google.com');
// spawn
spawn('echo', ['google.com']);
从执行的结果看,exec 和 spawn 都可以执行指定的命令。那么这两个方法的差异点在哪里?
参数
首先是他们的使用时参数不一样:
- exec(command) 的参数
command可以以字符串的形式拼接参数,因此可以像平常在bash中敲命令一般输入完整带参的命令内容,例如echo google.com; - spawn(command, args?) 的参数中区分了命令
command以及针对该命令附加的参数列表args?。因此必须分别定义传递,例如spawn('echo', ['google.com'])。
输出使用
其次,他们对被执行的命令的输出结果的使用方式也不一样:
-
exec()通常应配合着其回调参数使用。例如:exec('echo google.com', (error, stdout, stderr) => { if (error) { console.error(error); } console.log('stdout', stdout); console.error('stderr', stderr); })这是一种典型的 node.js 式的回调。可以使用
utils.promisify将其转换为基于 promise 的形式:const exec = utils.promisify(require('child_process').exec); exec('echo google.com').then(({ stdout, stderr }) => { console.log('stdout', stdout); console.error('stderr', stderr); });可以注意到,在回调结果里通过
stdoutstderr拿到的始终是这个命令尽可能完成执行之后所产生的全部buffer。假如我们执行的是一个长期存活命令例如ping google.com,那么除非命令中途出错退出,或者命令在stdout产生的信息达到了调用命令时设定的允许的buffer最大值(默认 1024x1024),否则我们没有直接的办法能够看到命令的是实时输出。 -
spawn()通常直接使用返回结果——一个 ChildProcess 实例。例如:const cp = spawn('echo', ['google.com']); cp.stdout.on('data', chunk => { console.log('chunk:', chunk.toString()); })在返回的
ChildProcess实例对象上具有stdoutstderr这两个可读流(ReadableStream),想要获得被执行命令的输出结果,只能通过监听流的数据写入事件得知。同上面的exec对比可知,当我们希望能够实时获取子进程命令执行结果的时候,用spawn会更加的合适。
同步版本
exec 和 spawn 各自拥有其同步版本的方法 execSync 和 spawnSync。我们知道,同步代码意味着阻塞,意味着在同步调用之后的代码,应该能确定前面的代码意图是执行完成。例如:
const a = execSync('echo google.com');
console.log(a.toString()); // google.com
const b = spawnSync('echo', ['google.com']);
console.log(b.stdout.toString()) // google.com
换成会长期存活的命令试一试:
var a = exec('ping google.com');
console.log(a.toString());
// OR:
// var b = spawn('ping', ['google.com']);
// console.log('b...', b.stdout.toString());
结果如预期所想的那样,程序一直到出错前都没有输出任何实时的信息。这说明在同步调用时,两种方式基本形式达成了一致。