借助exec
和spawn
,我们可以在node中运行执行控制台命令。
异步和同步执行
exec
之于execSync
,spawn
之于spawnSync
,是各自的异步与同步命令。
用多了就会觉得,异步执行的灵活性和可操作性,比同步执行要高很多。
同步执行
// execSync.js
const { execSync } = require('child_process')
const data = execSync('echo 123')
console.log('terminal:', data.toString('utf-8'))
执行
% node execSync.js
terminal: 123
execSync
会把控制台执行输出一次性吐成Buffer
返回,这样就意味着,同步执行不能收集过多的信息,否则有可能内存溢出。
spawnSysc
的主要区别,在于其执行的每个指令,都必须用数组单独存储:
const { spawnSync } = require('child_process')
const data = spawnSync('ls', ['-l', '/tmp'])
console.log(data.stdout.toString('utf-8'))
注意这里返回的结构有所不同,相比execSync
而言,如果要执行较大输出量的命令,spawnSync
更为合适。
执行:
% node spawnSync.js
lrwxr-xr-x@ 1 root wheel 11 1 1 2020 /tmp -> private/tmp
异步执行
exec
或spawn
异步执行会返回一个任务流。针对该流进行操作,可以接驳、串联各种操作。
// exec.js
const { exec, spawn } = require('child_process')
const task = exec(`curl -h`, { encoding: 'utf-8' })
task.stdout.on('data', chunk => {
console.log(chunk)
})
上面的代码,通过data
事件抓取任务流的输出,而后用主进程的console.log
输出到屏幕,实际上就是将子任务与主进程的一种接驳方式。
更直接的,也可以直接接驳到process.stdout
:
spawn('ls', ['-l'], { stdio: [process.stdin, process.stdout, process.stderr, 'ipc'] })
这里的输入输出,直接桥接到主进程的io中,不必再使用data
+ cosnole.log
监听。
fork
如果不需要带参数运行,可直接使用fork
来运行文件。
// pulse.js
setInterval(() => {
const now = Date.now()
process.send ? process.send(now + ':send') : console.log(now + ':log')
}, 1000)
直接运行该文件,会每隔1秒打印一个时间戳。下面是fork调用代码:
// fork.js
const { fork } = require('child_process')
const task = fork('./pulse.js')
task.on('message', msg => {
console.log('msg', msg)
})
注意:当fork
一个文件时,子进程会与主进程建立ipc
连接,子进程的process
则会具备process.send
方法。这个以后会用到。
fork
对问答模式也可无缝对接。
// qa.js
const readline = require('readline')
const q = readline.createInterface(process.stdin, process.stdout)
q.question('Your name?\n', a => {
console.log('Hello ' + a)
q.close()
})
// fork.js
const { fork } = require('child_process')
fork('./qa.js')
使用IPC实现自动交互
刚才的qa.js
文件,交互过程是:先提示输入用户名,等用户输入后,再打印Hello xxx
。
% node qa
Your name?
dog
Hello dog
这种交互需要用户输入,有时比较繁琐,尤其是需要CI的时候,如果工具没有-y
参数,集成不容易实现。
这时候我们可以通过创建一个子进程,在子进程与主进程之间建立ipc
通道;主进程根据子进程的提示,自动写入相应的内容。
// auto.js
const { exec } = require('child_process')
const child = exec('node ./qa.js', {
stdio: [null, null, null, 'ipc'], // 这里开启ipc
})
child.stdout.on('data', chunk => {
const msg = chunk.toString('utf-8')
console.log(msg.trim())
if(msg.startsWith('Your')) {
setTimeout(() => {
child.stdin.write('dog2\n') // 这里写入子进程交互内容,注意结尾要带个换行符
}, 200)
} else if(msg.startsWith('Hello')) {
process.exit(0)
}
})
看下运行效果
% node auto
Your name?
Hello dog2
实战自动初始化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')
}
})
控制台输出
% node auto-init.js
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (git)
> auto-init
version: (1.0.0)
> 1.2.3
description:
> auto-init o~ init
entry point: (1.js)
> ./dist/index.js
test command:
> exit(1)
git repository:
> git:xxx
keywords:
> exec spawn fork node npm
author:
> dog@dog.com
license: (ISC)
> MIT
About to write to /Users/dog/package.json:
{
"name": "auto-init",
"version": "1.2.3",
"description": "auto-init o~ init",
"main": "./dist/index.js",
"scripts": {
"test": "exit(1)"
},
"repository": {
"type": "git",
"url": "git:xxx"
},
"keywords": [
"exec",
"spawn",
"fork",
"node",
"npm"
],
"author": "dog@dog.com",
"license": "MIT"
}
Is this OK? (yes)
> yes
生成的package.json
{
"name": "auto-init",
"version": "1.2.3",
"description": "auto-init o~ init",
"main": "./dist/index.js",
"scripts": {
"test": "exit(1)"
},
"repository": {
"type": "git",
"url": "git:xxx"
},
"keywords": [
"exec",
"spawn",
"fork",
"node",
"npm"
],
"author": "dog@dog.com",
"license": "MIT"
}
以上。