Node.js spawn child process and get terminal output live
It's much easier now (6 years later)!
Spawn returns a childObject, which you can then listen for events with. The events are:
- Class: ChildProcess
- Event: 'error'
- Event: 'exit'
- Event: 'close'
- Event: 'disconnect'
- Event: 'message'
There are also a bunch of objects from childObject, they are:
- Class: ChildProcess
- child.stdin
- child.stdout
- child.stderr
- child.stdio
- child.pid
- child.connected
- child.kill([signal])
- child.send(message[, sendHandle][, callback])
- child.disconnect()
See more information here about childObject: https://nodejs.org/api/child_process.html
Asynchronous
If you want to run your process in the background while node is still able to continue to execute, use the asynchronous method. You can still choose to perform actions after your process completes, and when the process has any output (for example if you want to send a script's output to the client).
child_process.spawn(...); (Node v0.1.90)
var spawn = require('child_process').spawn;
var child = spawn('node ./commands/server.js');
// You can also use a variable to save the output
// for when the script closes later
var scriptOutput = "";
child.stdout.setEncoding('utf8');
child.stdout.on('data', function(data) {
//Here is where the output goes
console.log('stdout: ' + data);
data=data.toString();
scriptOutput+=data;
});
child.stderr.setEncoding('utf8');
child.stderr.on('data', function(data) {
//Here is where the error output goes
console.log('stderr: ' + data);
data=data.toString();
scriptOutput+=data;
});
child.on('close', function(code) {
//Here you can get the exit code of the script
console.log('closing code: ' + code);
console.log('Full output of script: ',scriptOutput);
});
Here's how you would use a callback + asynchronous method:
var child_process = require('child_process');
console.log("Node Version: ", process.version);
run_script("ls", ["-l", "/home"], function(output, exit_code) {
console.log("Process Finished.");
console.log('closing code: ' + exit_code);
console.log('Full output of script: ',output);
});
console.log ("Continuing to do node things while the process runs at the same time...");
// This function will output the lines from the script
// AS is runs, AND will return the full combined output
// as well as exit code when it's done (using the callback).
function run_script(command, args, callback) {
console.log("Starting Process.");
var child = child_process.spawn(command, args);
var scriptOutput = "";
child.stdout.setEncoding('utf8');
child.stdout.on('data', function(data) {
console.log('stdout: ' + data);
data=data.toString();
scriptOutput+=data;
});
child.stderr.setEncoding('utf8');
child.stderr.on('data', function(data) {
console.log('stderr: ' + data);
data=data.toString();
scriptOutput+=data;
});
child.on('close', function(code) {
callback(scriptOutput,code);
});
}
Using the method above, you can send every line of output from the script to the client (for example using Socket.io to send each line when you receive events on stdout
or stderr
).
Synchronous
If you want node to stop what it's doing and wait until the script completes, you can use the synchronous version:
child_process.spawnSync(...); (Node v0.11.12+)
Issues with this method:
- If the script takes a while to complete, your server will hang for that amount of time!
- The stdout will only be returned once the script has finished running. Because it's synchronous, it cannot continue until the current line has finished. Therefore it's unable to capture the 'stdout' event until the spawn line has finished.
How to use it:
var child_process = require('child_process');
var child = child_process.spawnSync("ls", ["-l", "/home"], { encoding : 'utf8' });
console.log("Process finished.");
if(child.error) {
console.log("ERROR: ",child.error);
}
console.log("stdout: ",child.stdout);
console.log("stderr: ",child.stderr);
console.log("exist code: ",child.status);
Here is the cleanest approach I've found:
require("child_process").spawn('bash', ['./script.sh'], {
cwd: process.cwd(),
detached: true,
stdio: "inherit"
});
I'm still getting my feet wet with Node.js, but I have a few ideas. first, I believe you need to use execFile
instead of spawn
; execFile
is for when you have the path to a script, whereas spawn
is for executing a well-known command that Node.js can resolve against your system path.
1. Provide a callback to process the buffered output:
var child = require('child_process').execFile('path/to/script', [
'arg1', 'arg2', 'arg3',
], function(err, stdout, stderr) {
// Node.js will invoke this callback when process terminates.
console.log(stdout);
});
2. Add a listener to the child process' stdout stream (9thport.net)
var child = require('child_process').execFile('path/to/script', [
'arg1', 'arg2', 'arg3' ]);
// use event hooks to provide a callback to execute when data are available:
child.stdout.on('data', function(data) {
console.log(data.toString());
});
Further, there appear to be options whereby you can detach the spawned process from Node's controlling terminal, which would allow it to run asynchronously. I haven't tested this yet, but there are examples in the API docs that go something like this:
child = require('child_process').execFile('path/to/script', [
'arg1', 'arg2', 'arg3',
], {
// detachment and ignored stdin are the key here:
detached: true,
stdio: [ 'ignore', 1, 2 ]
});
// and unref() somehow disentangles the child's event loop from the parent's:
child.unref();
child.stdout.on('data', function(data) {
console.log(data.toString());
});