io.js
通過child_process
模塊提供了三向的popen
功能。
可以無阻塞地通過子進(jìn)程的stdin
,stdout
和stderr
以流的方式傳遞數(shù)據(jù)。(注意某些程序在內(nèi)部使用了行緩沖 I/O,這不會影響io.js
,但是這意味你傳遞給子進(jìn)程的數(shù)據(jù)可能不會在第一時間被消費(fèi))。
可以通過require('child_process').spawn()
或require('child_process').fork()
創(chuàng)建子進(jìn)程。這兩者間的語義有少許差別,將會在后面進(jìn)行解釋。
當(dāng)以寫腳本為目的時,你可以會覺得使用同步版本的方法會更方便。
ChildProcess
是一個EventEmitter
。
子進(jìn)程總是有三個與之相關(guān)的流。child.stdin
,child.stdout
和child.stderr
。他們可能會共享父進(jìn)程的 stdio 流,或者也可以是獨(dú)立的被導(dǎo)流的流對象。
ChildProcess
類并不是用來直接被使用的。應(yīng)當(dāng)使用spawn()
,exec()
,execFile()
或fork()
方法來創(chuàng)建一個子進(jìn)程實(shí)例。
發(fā)生于:
進(jìn)程不能被創(chuàng)建時,進(jìn)程不能殺死時,給子進(jìn)程發(fā)送信息失敗時。
注意exit
事件在一個錯誤發(fā)生后可能觸發(fā)。如果你同時監(jiān)聽了這兩個事件來觸發(fā)一個函數(shù),需要記住不要讓這個函數(shù)被觸發(fā)兩次。
參閱 ChildProcess.kill()
和 ChildProcess.send()
。
注意子進(jìn)程的 stdio 流可能仍為打開狀態(tài)。
還需要注意的是,io.js
已經(jīng)為我們添加了'SIGINT'信號和'SIGTERM'信號的事件處理函數(shù),所以在父進(jìn)程發(fā)出這兩個信號時,進(jìn)程將會退出。
參閱 waitpid(2)
。
stdio
流都關(guān)閉時觸發(fā)。這是與exit
的區(qū)別,因?yàn)榭赡軙袔讉€進(jìn)程共享同樣的stdio
流。在父進(jìn)程或子進(jìn)程中使用.disconnect()
方法后這個事件會觸發(fā)。在斷開之后,將不能繼續(xù)相互發(fā)送信息,并且子進(jìn)程的.connected
屬性將會是false
。
Socket
或Server
對象通過.send(message, [sendHandle])
發(fā)送的信息可以通過監(jiān)聽message
事件獲取到。
一個代表了子進(jìn)程的stdin
的可寫流。通過end()
方法關(guān)閉此流可以終止子進(jìn)程。
如果子進(jìn)程通過spawn
創(chuàng)建時stdio
沒有被設(shè)置為pipe
,那么它將不會被創(chuàng)建。
child.stdin
為child.stdio
中對應(yīng)元素的快捷引用。它們要么都指向同一個對象,要么都為null。
一個代表了子進(jìn)程的stdout
的可讀流。
如果子進(jìn)程通過spawn
創(chuàng)建時stdio
沒有被設(shè)置為pipe
,那么它將不會被創(chuàng)建。
child.stdout
為child.stdio
中對應(yīng)元素的快捷引用。它們要么都指向同一個對象,要么都為null。
一個代表了子進(jìn)程的stderr
的可讀流。
如果子進(jìn)程通過spawn
創(chuàng)建時stdio
沒有被設(shè)置為pipe
,那么它將不會被創(chuàng)建。
child.stderr
為child.stdio
中對應(yīng)元素的快捷引用。它們要么都指向同一個對象,要么都為null。
一個包含了子進(jìn)程的管道的稀疏數(shù)組,元素的位置對應(yīng)著利用spawn
創(chuàng)建子進(jìn)程時stdio
配置參數(shù)里被設(shè)置為pipe
的位置。注意索引為0-2的流分別與ChildProcess.stdin
, ChildProcess.stdout
和ChildProcess.stderr
引用的是相同的對象。
在下面的例子中,在stdio
參數(shù)中只有索引為1的元素被設(shè)置為了pipe
,所以父進(jìn)程中只有child.stdio[1]
是一個流,其他的元素都為null
。
var assert = require('assert');
var fs = require('fs');
var child_process = require('child_process');
child = child_process.spawn('ls', {
stdio: [
0, // use parents stdin for child
'pipe', // pipe child's stdout to parent
fs.openSync('err.out', 'w') // direct child's stderr to a file
]
});
assert.equal(child.stdio[0], null);
assert.equal(child.stdio[0], child.stdin);
assert(child.stdout);
assert.equal(child.stdio[1], child.stdout);
assert.equal(child.stdio[2], null);
assert.equal(child.stdio[2], child.stderr);
子進(jìn)程的PID
。
例子:
var spawn = require('child_process').spawn,
grep = spawn('grep', ['ssh']);
console.log('Spawned child pid: ' + grep.pid);
grep.stdin.end();
.disconnect
方法被調(diào)用后將會被設(shè)置為false
。如果.connected
屬性為false
,那么將不能再向子進(jìn)程發(fā)送信息。給子進(jìn)程傳遞一個信號。如果沒有指定任何參數(shù),那么將發(fā)送'SIGTERM'
給子進(jìn)程。更多可用的信號請參閱signal(7)
。
var spawn = require('child_process').spawn,
grep = spawn('grep', ['ssh']);
grep.on('close', function (code, signal) {
console.log('child process terminated due to receipt of signal ' + signal);
});
// send SIGHUP to process
grep.kill('SIGHUP');
在信號不能被送達(dá)時,可能會產(chǎn)生一個error
事件。給一個已經(jīng)終止的子進(jìn)程發(fā)送一個信號不會發(fā)生錯誤,但可以操作不可預(yù)料的后果:如果該子進(jìn)程的PID
已經(jīng)被重新分配給了另一個進(jìn)程,那么這個信號會被傳遞到另一個進(jìn)程中。大家可以猜想這將會發(fā)生什么樣的情況。
注意這個函數(shù)僅僅是名字叫 kill,給子進(jìn)程發(fā)送的信號可能不是去關(guān)閉它的。這個函數(shù)僅僅只是給子進(jìn)程發(fā)送一個信號。
參閱kill(2)
。
當(dāng)使用child_process.fork()
時,你可以使用child.send(message, [sendHandle])
向子進(jìn)程發(fā)送信息,子進(jìn)程里會觸發(fā)message
事件當(dāng)收到信息時。
例子:
var cp = require('child_process');
var n = cp.fork(__dirname + '/sub.js');
n.on('message', function(m) {
console.log('PARENT got message:', m);
});
n.send({ hello: 'world' });
子進(jìn)程代碼, sub.js
可能看起來類似這樣:
process.on('message', function(m) {
console.log('CHILD got message:', m);
});
process.send({ foo: 'bar' });
在子進(jìn)程中,process
對象將有一個send()
方法,在它的信道上收到一個信息時,信息將以對象的形式返回。
請注意父進(jìn)程,子進(jìn)程中的send()
方法都是同步的,所以發(fā)送大量數(shù)據(jù)是不被建議的(可以使用管道代替,參閱child_process.spawn
)。
發(fā)送{cmd: 'NODE_foo'}
信息時是一個特殊情況。所有的在cmd
屬性中包含了NODE_
前綴的信息都不會觸發(fā)message
事件,因?yàn)檫@是io.js
內(nèi)核使用的內(nèi)部信息。包含這個前綴的信息都會觸發(fā)internalMessage
事件。請避免使用這個事件,它在改變的時候不會收到通知。
child.send()
的sendHandle
參數(shù)時用來給另一個進(jìn)程發(fā)送一個TCP服務(wù)器
或一個socket
的。將之作為第二個參數(shù)傳入,子進(jìn)程將在message
事件中會收到這個對象。
如果信息不能被發(fā)送的話將會觸發(fā)一個error
事件,比如子進(jìn)程已經(jīng)退出了。
例子:發(fā)送一個server
對象
var child = require('child_process').fork('child.js');
// Open up the server object and send the handle.
var server = require('net').createServer();
server.on('connection', function (socket) {
socket.end('handled by parent');
});
server.listen(1337, function() {
child.send('server', server);
});
子進(jìn)程將會收到server
對象:
process.on('message', function(m, server) {
if (m === 'server') {
server.on('connection', function (socket) {
socket.end('handled by child');
});
}
});
注意這個server
現(xiàn)在已經(jīng)被父進(jìn)程和子進(jìn)程所共享,這意味著鏈接將可能被父進(jìn)程處理也可能被子進(jìn)程處理。
對于dgram
服務(wù)器,流程也是完全一樣的。使用message
事件而不是connection
事件,使用server.bind
問不是server.listen
(目前只支持UNIX
平臺)。
例子:發(fā)送一個socket
對象
以下是發(fā)送一個socket
的例子。創(chuàng)建了兩個子進(jìn)程。并且將地址為74.125.127.100
的鏈接通過將socket
發(fā)送給"special"子進(jìn)程來視作 VIP。其他的socket
則被發(fā)送給"normal"子進(jìn)程。
var normal = require('child_process').fork('child.js', ['normal']);
var special = require('child_process').fork('child.js', ['special']);
// Open up the server and send sockets to child
var server = require('net').createServer();
server.on('connection', function (socket) {
// if this is a VIP
if (socket.remoteAddress === '74.125.127.100') {
special.send('socket', socket);
return;
}
// just the usual dudes
normal.send('socket', socket);
});
server.listen(1337);
`child.js`:
process.on('message', function(m, socket) {
if (m === 'socket') {
socket.end('You were handled as a ' + process.argv[2] + ' person');
}
});
注意一旦一個單獨(dú)的socket
被發(fā)送給了子進(jìn)程,那么父進(jìn)程將不能追蹤到這個socket
被刪除的時間,這個情況下.connections
屬性將會成為null
。在這個情況下同樣也不推薦使用.maxConnections
屬性。
關(guān)閉父進(jìn)程與子進(jìn)程間的 IPC 信道,它讓子進(jìn)程非常優(yōu)雅地退出,因?yàn)橐呀?jīng)活躍的信道了。在調(diào)用了這個方法后,父進(jìn)程和子進(jìn)程的.connected
標(biāo)簽都會被設(shè)置為false
,將不能再發(fā)送信息。
disconnect
事件在進(jìn)程不再有消息接收時觸發(fā)。
注意,當(dāng)子進(jìn)程中有與父進(jìn)程通信的 IPC 信道時,你也可以在子進(jìn)程中調(diào)用process.disconnect()
。
以下方法遵循普遍的異步編程模式(接受一個回調(diào)函數(shù)或返回一個EventEmitter
)。
args Array 字符串參數(shù)數(shù)組
options Object
利用給定的命令以及參數(shù)執(zhí)行一個新的進(jìn)程,如果沒有參數(shù)數(shù)組,那么args
將默認(rèn)是一個空數(shù)組。
第三個參數(shù)時用來指定以為額外的配置,以下是它的默認(rèn)值:
{ cwd: undefined,
env: process.env
}
使用cwd
來指定子進(jìn)程的工作目錄。如果沒有指定,默認(rèn)值是當(dāng)前父進(jìn)程的工作目錄。
使用env
來指定子進(jìn)程中可用的環(huán)境變量,默認(rèn)值是process.env
。
Example of running ls -lh /usr, capturing stdout, stderr, and the exit code:
一個運(yùn)行ls -lh /usr
,獲取stdout
,stderr
和退出碼得例子:
var spawn = require('child_process').spawn,
ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
ls.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
ls.on('close', function (code) {
console.log('child process exited with code ' + code);
});
例子:一個非常精巧的運(yùn)行ps ax | grep ssh
的方式
var spawn = require('child_process').spawn,
ps = spawn('ps', ['ax']),
grep = spawn('grep', ['ssh']);
ps.stdout.on('data', function (data) {
grep.stdin.write(data);
});
ps.stderr.on('data', function (data) {
console.log('ps stderr: ' + data);
});
ps.on('close', function (code) {
if (code !== 0) {
console.log('ps process exited with code ' + code);
}
grep.stdin.end();
});
grep.stdout.on('data', function (data) {
console.log('' + data);
});
grep.stderr.on('data', function (data) {
console.log('grep stderr: ' + data);
});
grep.on('close', function (code) {
if (code !== 0) {
console.log('grep process exited with code ' + code);
}
});
一個檢查執(zhí)行失敗的例子:
var spawn = require('child_process').spawn,
child = spawn('bad_command');
child.on('error', function (err) {
console.log('Failed to start child process.');
});
作為快捷方式,stdio
的值可以是一下字符串之一:
'pipe' - ['pipe', 'pipe', 'pipe'], 這是默認(rèn)值 'ignore' - ['ignore', 'ignore', 'ignore'] 'inherit' - [process.stdin, process.stdout, process.stderr]或[0,1,2]
否則,child_process.spawn()
的stdio
參數(shù)時一個數(shù)組,數(shù)組中的每一個索引的對應(yīng)子進(jìn)程中的一個文件標(biāo)識符??梢允窍铝兄抵唬?/p>
'pipe' - 創(chuàng)建一個子進(jìn)程與父進(jìn)程之間的管道,管道的父進(jìn)程端已父進(jìn)程的child_process
對象的屬性(ChildProcess.stdio[fd]
)暴露給父進(jìn)程。為文件表示(fds)0 - 2 創(chuàng)建的管道也可以通過ChildProcess.stdin
,ChildProcess.stdout
和ChildProcess.stderr
分別訪問。
'ipc' - 創(chuàng)建一個子進(jìn)程和父進(jìn)程間 傳輸信息/文件描述符 的 IPC 信道。一個子進(jìn)程最多可能有一個IPC stdio 文件描述符。設(shè)置該選項(xiàng)將激活ChildProcess.send()
方法。如果子進(jìn)程向此文件描述符中寫入 JSON 數(shù)據(jù),則會觸發(fā) ChildProcess.on('message')
。如果子進(jìn)程是一個io.js
程序,那么IPC信道的存在將會激活process.send()
和process.on('message')
。
'ignore' - 不在子進(jìn)程中設(shè)置文件描述符。注意io.js
總是會為通過spawn
創(chuàng)建的子進(jìn)程打開文件描述符(fd) 0 - 2。如果這其中任意一項(xiàng)被設(shè)置為了ignore
,io.js
會打開/dev/null
并將其附給子進(jìn)程對應(yīng)的文件描述符(fd)。
Stream object - 與子進(jìn)程共享一個與 tty,文件,socket,或管道相關(guān)的可讀/可寫流。該流底層(underlying)的文件標(biāo)識在子進(jìn)程中被復(fù)制給stdio數(shù)組索引對應(yīng)的文件描述符(fd)。
Positive integer - 該整形值被解釋為父進(jìn)程中打開的文件標(biāo)識符。他與子進(jìn)程共享,和 Stream 被共享的方式相似。
null, undefined - 使用默認(rèn)值。For 對于 stdio fds 0,1,2(或者說stdin
,stdout
和stderr
),pipe 管道被建立。對于 fd 3及往后,默認(rèn)為ignore
。
例子:
var spawn = require('child_process').spawn;
// Child will use parent's stdios
spawn('prg', [], { stdio: 'inherit' });
// Spawn child sharing only stderr
spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] });
// Open an extra fd=4, to interact with programs present a
// startd-style interface.
spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });
如果detached
選項(xiàng)被設(shè)置,子進(jìn)程將成為新進(jìn)程組的領(lǐng)導(dǎo)。這使得在父進(jìn)程退出后,子進(jìn)程繼續(xù)執(zhí)行成為可能。
默認(rèn)情況下,父進(jìn)程會等待脫離了的子進(jìn)程退出。要阻止父進(jìn)程等待一個給出的子進(jìn)程,請使用child.unref()
方法,則父進(jìn)程的事件循環(huán)的計(jì)數(shù)中將不包含這個子進(jìn)程。
一個脫離的長時間運(yùn)行的進(jìn)程,以及將它的輸出重定向到文件中的例子:
var fs = require('fs'),
spawn = require('child_process').spawn,
out = fs.openSync('./out.log', 'a'),
err = fs.openSync('./out.log', 'a');
var child = spawn('prg', [], {
detached: true,
stdio: [ 'ignore', out, err ]
});
child.unref();
當(dāng)使用detached
選項(xiàng)創(chuàng)建一個長時間運(yùn)行的進(jìn)程時,進(jìn)程不會保持運(yùn)行除非向它提供了一個不連接到父進(jìn)程的stdio
的配置。如果繼承了父進(jìn)程的stdio
,那么子進(jìn)程將會繼續(xù)附著在控制終端。
參閱: child_process.exec()
和 child_process.fork()
command String 將要運(yùn)行的命令,參數(shù)使用空格隔開
options Object
/bin/sh
, 在 Windows 中為cmd.exe
, Shell 應(yīng)當(dāng)能識別 -c
開關(guān)在 UNIX 中,或 /s /c
在 Windows 中。 在Windows 中,命令行解析應(yīng)當(dāng)能兼容cmd.exe
)在 Shell 中運(yùn)行一個命令,并緩存命令的輸出。
var exec = require('child_process').exec,
child;
child = exec('cat *.js bad_file | wc -l',
function (error, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if (error !== null) {
console.log('exec error: ' + error);
}
});
回調(diào)函數(shù)的參數(shù)是error
,stdout
,stderr
。在成功時,error
將會是null
。在發(fā)生錯誤時,error
將會是一個Error
實(shí)例,error.code
將會是子進(jìn)程的退出碼,error.signal
將會被設(shè)置為結(jié)束進(jìn)程的信號。
第二個可選的參數(shù)用于指定一些配置,默認(rèn)值為:
{ encoding: 'utf8',
timeout: 0,
maxBuffer: 200*1024,
killSignal: 'SIGTERM',
cwd: null,
env: null }
如果timeout
大于0,那么子進(jìn)程在運(yùn)行時超過timeout
時將會被殺死。子進(jìn)程使用killSignal
信號結(jié)束(默認(rèn)為: 'SIGTERM')。maxBuffer
指定了stdout
,stderr
中的最大數(shù)據(jù)量(字節(jié)),如果超過了這個數(shù)據(jù)量子進(jìn)程也會被殺死。
注意:不像 POSIX 中的exec()
,child_process.exec()
不替換已經(jīng)存在的進(jìn)程并且使用一個SHELL 去執(zhí)行命令。
args 字符串參數(shù)數(shù)組
callback Function
這個方法和child_process.exec()
相似,除了它不是使用一個子 SHELL 執(zhí)行命令而是直接執(zhí)行文件。因此它比child_process.exec
稍許精簡一些。它們有相同的配置。
args Array 字符串參數(shù)數(shù)組
true
,子進(jìn)程的stdin
,stdout
和stderr
將會被關(guān)聯(lián)至父進(jìn)程,否則,它們將會從父進(jìn)程中繼承。(默認(rèn)為:false
)Return: ChildProcess object
這個方法是spawn()
的特殊形式,用于創(chuàng)建io.js
進(jìn)程。返回的對象除了擁有ChildProcess
實(shí)例的所有方法,還有一個內(nèi)建的通信信道。詳情參閱child.send(message, [sendHandle])
。
這些io.js
子進(jìn)程都是全新的 V8 實(shí)例。每個新的io.js
進(jìn)程都至少需要30ms 啟動以及10mb 的內(nèi)存。所以,你不能無休止地創(chuàng)建它們。
options
對象中的execPath
屬性可以用非當(dāng)前io.js
可執(zhí)行文件來創(chuàng)建子進(jìn)程。這需要小心使用,并且缺省情況下會使用子進(jìn)程上的NODE_CHANNEL_FD
環(huán)境變量所指定的文件描述符來通訊。該文件描述符的輸入和輸出假定為以行分割的 JSON 對象。
注意:不像 POSIX 中的fork()
,child_process.fork()
不會復(fù)制當(dāng)前進(jìn)程。
以下這些方法是同步的,意味著它們會阻塞事件循環(huán)。直到被創(chuàng)建的進(jìn)程退出前,代碼都將停止執(zhí)行。
這些同步方法對簡化大多數(shù)腳本任務(wù)都十分有用,并對簡化應(yīng)用配置的加載/執(zhí)行也之分有用。
args Array 字符串參數(shù)數(shù)組
options Object
stdin
傳入被創(chuàng)建的進(jìn)程的值,提供這個值將會覆蓋stdio[0]
stdio
配置undefined
)stdio
輸入和輸出的編碼(默認(rèn):'buffer')stdio
輸出結(jié)果的數(shù)組stdout
的內(nèi)容stderr
的內(nèi)容spawnSync
會在子進(jìn)程完全結(jié)束后才返回。當(dāng)運(yùn)行超時或被傳遞killSignal
時,這個方法會等到進(jìn)程完全退出才返回。也就是說,如果子進(jìn)程處理了SIGTERM
信號并且沒有退出,你的父進(jìn)程會繼續(xù)阻塞。
args Array 字符串參數(shù)數(shù)組
options Object
stdin
傳入被創(chuàng)建的進(jìn)程的值,提供這個值將會覆蓋stdio[0]
stdio
配置(默認(rèn): 'pipe'),stderr
默認(rèn)得將會輸出到父進(jìn)程的stderr
,除非指定了stdio
undefined
)stdio
輸入和輸出的編碼(默認(rèn):'buffer')stdout
execFileSync
會在子進(jìn)程完全結(jié)束后才返回。當(dāng)運(yùn)行超時或被傳遞killSignal
時,這個方法會等到進(jìn)程完全退出才返回。也就是說,如果子進(jìn)程處理了SIGTERM
信號并且沒有退出,你的父進(jìn)程會繼續(xù)阻塞。
如果子進(jìn)程超時或有一個非零的狀態(tài)碼,這個方法會拋出一個錯誤。這個錯誤對象與child_process.spawnSync
的錯誤對象相同。
command 將要運(yùn)行的命令
options Object
stdin
傳入被創(chuàng)建的進(jìn)程的值,提供這個值將會覆蓋stdio[0]
stdio
配置(默認(rèn): 'pipe'),stderr
默認(rèn)得將會輸出到父進(jìn)程的stderr
,除非指定了stdio
undefined
)stdio
輸入和輸出的編碼(默認(rèn):'buffer')stdout
execSync
會在子進(jìn)程完全結(jié)束后才返回。當(dāng)運(yùn)行超時或被傳遞killSignal
時,這個方法會等到進(jìn)程完全退出才返回。也就是說,如果子進(jìn)程處理了SIGTERM
信號并且沒有退出,你的父進(jìn)程會繼續(xù)阻塞。
如果子進(jìn)程超時或有一個非零的狀態(tài)碼,這個方法會拋出一個錯誤。這個錯誤對象與child_process.spawnSync
的錯誤對象相同。