child_process
基础概念
核心API
语法区别
以同一个命令来展示:node ./dir/test1/js
const cp = require('child_process');
// spawn
cp.spawn('node', ['./dir/test1.js'],
{ stdio: 'inherit' }
);
// exec
cp.exec('node ./dir/test1.js', (err, stdout, stderr) => {
console.log(stdout);
});
// execFile
cp.execFile('node', ['./dir/test1.js'],(err, stdout, stderr) => {
console.log(stdout);
});
// fork
cp.fork('./dir/test1.js',
{ silent: false }
);
// ./dir/test1.js
console.log('test1 输出...');
promise + async
import cp from 'child_process'
import { promisify } from 'util';
// 以下所有错误都要通过try..catch来捕获
// spawn
const { stderr, stdout } = promisify(cp.spawn)('node', ['./dir/test1.js'], { stdio: 'inherit' });
// exec
const {stderr, stdout, code } = promisify(cp.exec)('node', ['./dir/test1.js']);
// execFile
const { stderr, stdout } = promisify(cp.execFile)('node', ['./dir/test1.js']);
// fork
const { stderr, stdout } = promisify(cp.fork)('node', ['./dir/test1.js'], { silent: false });
基础差异点
类型 | 回调/异常 | 进程类型 | 执行类型 | 可设置超时 |
---|---|---|---|---|
spawn | ⛔ | 任意 | 命令 | ⛔ |
exec | ✔️ | 任意 | 命令 | ✔️ |
execFile | ✔️ | 任意 | 可执行文件 | ✔️ |
fork | ⛔ | Node | JavaScript | 文件 |
- spawn 与 exec、execFile 不同的是,后两者创建时可以指定 timeout 属性设置超时时间,一旦创建的进程运行超过设定的时间将会被杀死;
- exec 与 execFile 不同的是,exec 适合执行已有的命令,execFile 适合执行文件;
- exec、execFile、fork 都是 spawn 的延伸应用,底层都是通过 spawn 实现的;
exec
child_process.exec方法是“同步中的异步”,意思是尽管exec是异步的,它一定要等到子进程运行结束以后然后一次性返回所有的buffer数据。 如果exec的buffer体积设置的不够大,它将会以一个“maxBuffer exceeded”错误失败告终
child_process.exec方法会从子进程中返回一个完整的buffer。默认情况下,这个buffer的大小应该是200k。 如果子进程返回的数据大小超过了200k,程序将会崩溃,同时显示错误信息“Error:maxBuffer exceeded”。 你可以通过在exec的可选项中设置一个更大的buffer体积来解决这个问题,但是你不应该这样做,因为exec本来就不是用来返回很多数据的方法。 对于有很多数据返回的情况,你应该使用上面的spawn方法。那么exec究竟是用来做什么的呢?我们可以使用它来运行程序然后返回结果的状态,而 不是结果的数据
var cp = require('child_process');
var ls = cp.exec('ls -lh /usr', {}/*options, [optional]*/);
ls.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
ls.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
ls.on('exit', function (code) {
console.log('child process exited with code ' + code);
});
spawn
child_process.spawn会返回一个带有stdout和stderr流的对象。你可以通过stdout流来读取子进程返回给Node.js的数据。 stdout拥有’data’,’end’以及一般流所具有的事件。当你想要子进程返回大量数据给Node时,比如说图像处理,读取二进制数 据等等,你最好使用spawn方法 child_process.spawn方法是“异步中的异步”,意思是在子进程开始执行时,它就开始从一个流总将数据从子进程返回给No
var cp = require('child_process');
var ls = cp.spawn('ls'/*command*/, ['-lh', '/usr']/*args*/, {}/*options, [optional]*/);
ls.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
ls.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
ls.on('exit', function (code) {
console.log('child process exited with code ' + code);
})
通过IPC与NPM指令实现自动交互
截取控制台的输出,根据不同的阶段进行不同的响应
// auto-init.js
const { exec } = require('child_process')
const child = exec('npm init', { stdio: [null, null, null, 'ipc'] })
child.stdout.on('data', chunk => {
const msg = chunk.toString('utf-8')
console.log(msg.trim())
if(msg.startsWith('package name')) {
const val = 'auto-init'
console.log('>', val)
child.stdin.write(val + '\n')
}
if(msg.startsWith('version')) {
const val = '1.2.3'
console.log('>', val)
child.stdin.write(val + '\n')
}
if(msg.startsWith('description')) {
const val = 'auto-init o~ init'
console.log('>', val)
child.stdin.write(val + '\n')
}
if(msg.startsWith('entry point')) {
const val = './dist/index.js'
console.log('>', val)
child.stdin.write(val + '\n')
}
if(msg.startsWith('test command')) {
const val = 'exit(1)'
console.log('>', val)
child.stdin.write(val + '\n')
}
if(msg.startsWith('git repository')) {
const val = 'git:xxx'
console.log('>', val)
child.stdin.write(val + '\n')
}
if(msg.startsWith('keywords')) {
const val = 'exec spawn fork node npm'
console.log('>', val)
child.stdin.write(val + '\n')
}
if(msg.startsWith('author')) {
const val = 'dog@dog.com'
console.log('>', val)
child.stdin.write(val + '\n')
}
if(msg.startsWith('license')) {
const val = 'MIT'
console.log('>', val)
child.stdin.write(val + '\n')
}
if(msg.startsWith('Is this OK')) {
const val = ''
console.log('>', val)
child.stdin.write(val + '\n')
}
})
常用案例
同步
/**
* @Description - 同步执行运行shell/bash等指令
*/
export const shellSync = (commands: string[]) => {
const commands_str = commands.join(' ');
try {
const { status, stdout, stderr } = child_process.spawnSync(commands_str);
if (status == 0) {
if (stdout) return { success: true, res: stdout.toString().trim() };
if (stderr) return { success: false, err: stderr.toString().trim() };
}
return { success: false };
} catch (e: any) {
return { success: false, err: e.toString().trim() };
}
};
异步
interface ShellOptions {
encoding?: string;
windowsHide?: boolean;
cwd?: string | undefined;
}
/**
* @Description - 异步执行运行shell/bash等指令
*
* @param {string[]} commands - 列表形式的命令
* @example
* ```js
* // 单条命令
* let commands = ['git', 'add', '.', '&', 'git', 'commit', '-m', 'cps-cli-before-pull', '&', 'git', 'pull', 'origin', 'master']
await shell(command, { cwd })
// 多条命令
const commands = [
['git', 'add', '.'],
['git', 'commit', '-m', 'cps-cli-push'],
['git', 'push', 'origin', 'master'],
];
for (let command of commands) {
await shell(command, { cwd });
}
* ```
*/
export const shell = async (commands: string[], options?: ShellOptions) => {
const default_options: ShellOptions = { encoding: 'utf-8', windowsHide: true, cwd: undefined };
options = Object.assign(default_options, options);
const commands_str = commands.join(' ');
try {
const { stdout, stderr } = await exec(commands_str, { ...options });
if (stdout) return { success: true, res: stdout.trim() };
if (stderr) return { success: true, res: stderr.toString().trim() };
return { success: false };
} catch (e: any) {
return { success: false, err: e.toString().trim() };
}
};