child_process用法
child_process的api:
- 異步api:
- exec(cmd, options, callback)
- execFile(cmd, args, options, callback)
- fork (模塊路徑, args, options) // 不一樣的地方在于可以通信
- spawn(cmd, args, options)
- exec用法:
執(zhí)行shell腳本, 使用管道符也是可以的
exec也是可以執(zhí)行文件的仪或,只不過不能傳參數(shù)
適合開銷比較小的任務(wù)
const cp = require('child_process') cp.exec('ls -al|grep node_modules', { timeout: 0, // 超時(shí)時(shí)間 cwd: process.cwd(), // 可以改變當(dāng)前的執(zhí)行路徑 }, function (err, stdout, stderr) { // 執(zhí)行結(jié)果 })
- execFile用法:
可以執(zhí)行文件悠鞍,也可以執(zhí)行語句创淡,可傳參
適合開銷比較小的任務(wù)
// 執(zhí)行文件,參數(shù) cp.execFile('ls', ['-al'], function (err, stdout, std,err) => { // 執(zhí)行結(jié)果 }) // 讓execFile執(zhí)行l(wèi)s -al|grep node_modules這種語句 test.shell: ls -al|grep node_modules echo $1 // 打印參數(shù) echo $2 index.js: cp.execFile(path.resolve(__dirname, 'test.shell'), ['-al', 'bl'], function(err, stdout, stderr) { })
- fork用法
用node執(zhí)行, 耗時(shí)操作且用node實(shí)現(xiàn)旁振,如下載文件
// cp.fork(模塊路徑) // 和require一樣把文件執(zhí)行起來 const child = cp.fork(path.resolve(__dirname, 'child_process')) console.log(process.pid) // 主進(jìn)程向子進(jìn)程通信 child.send('hello child_process', () => { // child.disconnent() // 如果不斷開,兩邊會(huì)出現(xiàn)等待的情況 }) // 子進(jìn)程向主進(jìn)程通信 child.on('message', msg => { }) // child_process.js: console.log('aaa', process.pid) process.on('message', msg => { console.log(msg) // 很容易出現(xiàn)死循環(huán) }) process.send('send msg to parent') // 進(jìn)程不一樣,完全獨(dú)立域那,本質(zhì)也是調(diào)用spawn
- spawn 用法
spawn: 流式的活逆,沒有回調(diào),適合耗時(shí)任務(wù)(比如:npm install), 需要不斷打印日志(不斷給用戶輸出日志)
cp.spawn(file, args, options) // 不支持回調(diào), exec,execFile底層都是spwan
const child = cp.spawn(path.resolve(__dirname, 'test.shell'), ['-al', '-bl'], {
cwd: path.resolve('..'),
}) // 返回的是子進(jìn)程
console.log(child.pid, process.pid)
// 監(jiān)聽成功
child.stdout.on('data', function(chunk) {
console.log(chunk.toString())
})
// 監(jiān)聽失敗
child.stderr.on('data', function(chunk) {
console.log(chunk.toString())
})
const code = `require('${rootFile}').call(null, ${JSON.stringify(args)})`
// cp.spawn('cmd', ['/c', 'node', '-e', code]) // win下是這種結(jié)構(gòu)
const child = spawn('node', ['-e', code], {
cwd: process.cwd(), // 當(dāng)前執(zhí)行未知的cwd
stdio: 'inherit', // 默認(rèn)是pipe,pipe必須通過on來接收信息剪决,inherit不需要灵汪,實(shí)時(shí)反饋
})
child.on('error', e => {
log.error(e.message)
process.exit(1)
})
child.on('exit', e => {
log.verbose('命令執(zhí)行成功', e)
process.exit(e)
})
- 同步api:
- execSync
- execFileSync
- spawnSync
const ret = cp.execSync('ls -al|grep node_modules') // 用的比較多,對腳本安全性沒有校驗(yàn)
// 可以直接拿到結(jié)果
console.log(ret.toString())
const ret2 = cp.execFileSync('ls', ['-al'])
console.log(ret2.toString)
const ret3 = cp.spawnSync('ls', ['-al'])
console.log(ret3.stdout.toString()) // 返回的是一個(gè)對象
child_process原理
1. exec 和 execFile有什么區(qū)別柑潦?
- 二者只有傳參不同
- 底層調(diào)用的還是spawn
// exec源碼享言,處理一下參數(shù),調(diào)用的execFile
function exec(command, options, callback) {
const opts = normalizeExecArgs(command, options, callback);
return module.exports.execFile(opts.file,
opts.options,
opts.callback);
}
function execFile(file /* , args, options, callback */) {
// ...
// 底層調(diào)用的還是spawn
const child = spawn(file, args, {
cwd: options.cwd,
env: options.env,
gid: options.gid,
shell: options.shell,
signal: options.signal,
uid: options.uid,
windowsHide: !!options.windowsHide,
windowsVerbatimArguments: !!options.windowsVerbatimArguments
});
}
- exec: 原理是調(diào)用/bin/sh -c 執(zhí)行我們傳入的shell腳本渗鬼,底層調(diào)用execFile
- execFile:原理是直接執(zhí)行我們傳入的file和args览露,底層調(diào)用spawn創(chuàng)建和執(zhí)行子進(jìn)程,并建立回調(diào)乍钻,一次性將所有的stdout和stderr結(jié)果返回
- spawn:原理是調(diào)用internal/child_process,實(shí)例化略ChildProcess子進(jìn)程對象肛循,再調(diào)用child.spawn創(chuàng)建 子進(jìn)程并執(zhí)行命令,底層是調(diào)用了child.)handle.spawn執(zhí)行process_wrap中的spwan方法银择,執(zhí)行過程是異步的多糠,執(zhí)行完畢后再通過PIPE進(jìn)行單向數(shù)據(jù)通信,通信結(jié)束后子進(jìn)程發(fā)起onexit回調(diào)浩考,同時(shí)Socket會(huì)執(zhí)行close回調(diào)夹孔。
- fork:原理是通過spawn創(chuàng)建子進(jìn)程和執(zhí)行命令,采用node執(zhí)行命令析孽,通過setupchannel創(chuàng)建IPC用于子進(jìn)程和父進(jìn)程之間的雙向通信搭伤。
2. data/error/exit/close回調(diào)的區(qū)別
data:用于主進(jìn)程讀取數(shù)據(jù)過程中通過onStreamRead發(fā)起的回調(diào)
error: 命令執(zhí)行失敗后發(fā)起的回調(diào)
exit: 子進(jìn)程關(guān)閉完成后發(fā)起的回調(diào)
close:子進(jìn)程所有Socket通信端口全部關(guān)閉后發(fā)起的回調(diào)
stdout close/stderr close:特定的PIPE讀取完成后調(diào)用onReadableStreamEnd關(guān)閉Socket時(shí)發(fā)起的回調(diào)。