diff --git a/README.md b/README.md index 1a3f702..980f536 100644 --- a/README.md +++ b/README.md @@ -481,23 +481,24 @@ Available options (all `false` by default): Examples: ```javascript -var version = exec('node --version', {silent:true}).output; +var version = exec('node --version', {silent:true}).stdout; var child = exec('some_long_running_process', {async:true}); child.stdout.on('data', function(data) { /* ... do something with data ... */ }); -exec('some_long_running_process', function(code, output) { +exec('some_long_running_process', function(code, stdout, stderr) { console.log('Exit code:', code); - console.log('Program output:', output); + console.log('Program output:', stdout); + console.log('Program stderr:', stderr); }); ``` -Executes the given `command` _synchronously_, unless otherwise specified. -When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's -`output` (stdout + stderr) and its exit `code`. Otherwise returns the child process object, and -the `callback` gets the arguments `(code, output)`. +Executes the given `command` _synchronously_, unless otherwise specified. When in synchronous +mode returns the object `{ code:..., stdout:... , stderr:... }`, containing the program's +`stdout`, `stderr`, and its exit `code`. Otherwise returns the child process object, +and the `callback` gets the arguments `(code, stdout, stderr)`. **Note:** For long-lived processes, it's best to run `exec()` asynchronously as the current synchronous implementation uses a lot of CPU. This should be getting diff --git a/src/common.js b/src/common.js index d8c2312..201fc91 100644 --- a/src/common.js +++ b/src/common.js @@ -21,7 +21,7 @@ exports.platform = platform; function log() { if (!config.silent) - console.log.apply(this, arguments); + console.error.apply(this, arguments); } exports.log = log; @@ -29,10 +29,14 @@ exports.log = log; function error(msg, _continue) { if (state.error === null) state.error = ''; - state.error += state.currentCmd + ': ' + msg + '\n'; + var log_entry = state.currentCmd + ': ' + msg; + if (state.error === '') + state.error = log_entry; + else + state.error += '\n' + log_entry; if (msg.length > 0) - log(state.error); + log(log_entry); if (config.fatal) process.exit(1); diff --git a/src/exec.js b/src/exec.js index d259a9f..87a6c30 100644 --- a/src/exec.js +++ b/src/exec.js @@ -13,6 +13,7 @@ var child = require('child_process'); function execSync(cmd, opts) { var tempDir = _tempDir(); var stdoutFile = path.resolve(tempDir+'/'+common.randomFileName()), + stderrFile = path.resolve(tempDir+'/'+common.randomFileName()), codeFile = path.resolve(tempDir+'/'+common.randomFileName()), scriptFile = path.resolve(tempDir+'/'+common.randomFileName()), sleepFile = path.resolve(tempDir+'/'+common.randomFileName()); @@ -21,19 +22,30 @@ function execSync(cmd, opts) { silent: common.config.silent }, opts); - var previousStdoutContent = ''; - // Echoes stdout changes from running process, if not silent - function updateStdout() { - if (options.silent || !fs.existsSync(stdoutFile)) + var previousStdoutContent = '', + previousStderrContent = ''; + // Echoes stdout and stderr changes from running process, if not silent + function updateStream(streamFile) { + if (options.silent || !fs.existsSync(streamFile)) return; - var stdoutContent = fs.readFileSync(stdoutFile, 'utf8'); + var previousStreamContent, + proc_stream; + if (streamFile === stdoutFile) { + previousStreamContent = previousStdoutContent; + proc_stream = process.stdout; + } else { // assume stderr + previousStreamContent = previousStderrContent; + proc_stream = process.stderr; + } + + var streamContent = fs.readFileSync(streamFile, 'utf8'); // No changes since last time? - if (stdoutContent.length <= previousStdoutContent.length) + if (streamContent.length <= previousStreamContent.length) return; - process.stdout.write(stdoutContent.substr(previousStdoutContent.length)); - previousStdoutContent = stdoutContent; + proc_stream.write(streamContent.substr(previousStreamContent.length)); + previousStreamContent = streamContent; } function escape(str) { @@ -42,6 +54,7 @@ function execSync(cmd, opts) { if (fs.existsSync(scriptFile)) common.unlinkSync(scriptFile); if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile); + if (fs.existsSync(stderrFile)) common.unlinkSync(stderrFile); if (fs.existsSync(codeFile)) common.unlinkSync(codeFile); var execCommand = '"'+process.execPath+'" '+scriptFile; @@ -59,14 +72,16 @@ function execSync(cmd, opts) { " fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0');", "});", "var stdoutStream = fs.createWriteStream('"+escape(stdoutFile)+"');", + "var stderrStream = fs.createWriteStream('"+escape(stderrFile)+"');", "childProcess.stdout.pipe(stdoutStream, {end: false});", - "childProcess.stderr.pipe(stdoutStream, {end: false});", + "childProcess.stderr.pipe(stderrStream, {end: false});", "childProcess.stdout.pipe(process.stdout);", "childProcess.stderr.pipe(process.stderr);", "var stdoutEnded = false, stderrEnded = false;", - "function tryClosing(){ if(stdoutEnded && stderrEnded){ stdoutStream.end(); } }", - "childProcess.stdout.on('end', function(){ stdoutEnded = true; tryClosing(); });", - "childProcess.stderr.on('end', function(){ stderrEnded = true; tryClosing(); });" + "function tryClosingStdout(){ if(stdoutEnded){ stdoutStream.end(); } }", + "function tryClosingStderr(){ if(stderrEnded){ stderrStream.end(); } }", + "childProcess.stdout.on('end', function(){ stdoutEnded = true; tryClosingStdout(); });", + "childProcess.stderr.on('end', function(){ stderrEnded = true; tryClosingStderr(); });" ].join('\n'); fs.writeFileSync(scriptFile, script); @@ -80,7 +95,7 @@ function execSync(cmd, opts) { // Welcome to the future child.execSync(execCommand, execOptions); } else { - cmd += ' > '+stdoutFile+' 2>&1'; // works on both win/unix + cmd += ' > '+stdoutFile+' 2> '+stderrFile; // works on both win/unix var script = [ "var child = require('child_process')", @@ -98,8 +113,9 @@ function execSync(cmd, opts) { // sleepFile is used as a dummy I/O op to mitigate unnecessary CPU usage // (tried many I/O sync ops, writeFileSync() seems to be only one that is effective in reducing // CPU usage, though apparently not so much on Windows) - while (!fs.existsSync(codeFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); } - while (!fs.existsSync(stdoutFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); } + while (!fs.existsSync(codeFile)) { updateStream(stdoutFile); fs.writeFileSync(sleepFile, 'a'); } + while (!fs.existsSync(stdoutFile)) { updateStream(stdoutFile); fs.writeFileSync(sleepFile, 'a'); } + while (!fs.existsSync(stderrFile)) { updateStream(stderrFile); fs.writeFileSync(sleepFile, 'a'); } } // At this point codeFile exists, but it's not necessarily flushed yet. @@ -110,10 +126,12 @@ function execSync(cmd, opts) { } var stdout = fs.readFileSync(stdoutFile, 'utf8'); + var stderr = fs.readFileSync(stderrFile, 'utf8'); // No biggie if we can't erase the files now -- they're in a temp dir anyway try { common.unlinkSync(scriptFile); } catch(e) {} try { common.unlinkSync(stdoutFile); } catch(e) {} + try { common.unlinkSync(stderrFile); } catch(e) {} try { common.unlinkSync(codeFile); } catch(e) {} try { common.unlinkSync(sleepFile); } catch(e) {} @@ -124,14 +142,17 @@ function execSync(cmd, opts) { // True if successful, false if not var obj = { code: code, - output: stdout + output: stdout, // deprecated + stdout: stdout, + stderr: stderr }; return obj; } // execSync() // Wrapper around exec() to enable echoing output to console in real time function execAsync(cmd, opts, callback) { - var output = ''; + var stdout = ''; + var stderr = ''; var options = common.extend({ silent: common.config.silent @@ -139,19 +160,19 @@ function execAsync(cmd, opts, callback) { var c = child.exec(cmd, {env: process.env, maxBuffer: 20*1024*1024}, function(err) { if (callback) - callback(err ? err.code : 0, output); + callback(err ? err.code : 0, stdout, stderr); }); c.stdout.on('data', function(data) { - output += data; + stdout += data; if (!options.silent) process.stdout.write(data); }); c.stderr.on('data', function(data) { - output += data; + stderr += data; if (!options.silent) - process.stdout.write(data); + process.stderr.write(data); }); return c; @@ -167,23 +188,24 @@ function execAsync(cmd, opts, callback) { //@ Examples: //@ //@ ```javascript -//@ var version = exec('node --version', {silent:true}).output; +//@ var version = exec('node --version', {silent:true}).stdout; //@ //@ var child = exec('some_long_running_process', {async:true}); //@ child.stdout.on('data', function(data) { //@ /* ... do something with data ... */ //@ }); //@ -//@ exec('some_long_running_process', function(code, output) { +//@ exec('some_long_running_process', function(code, stdout, stderr) { //@ console.log('Exit code:', code); -//@ console.log('Program output:', output); +//@ console.log('Program output:', stdout); +//@ console.log('Program stderr:', stderr); //@ }); //@ ``` //@ -//@ Executes the given `command` _synchronously_, unless otherwise specified. -//@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's -//@ `output` (stdout + stderr) and its exit `code`. Otherwise returns the child process object, and -//@ the `callback` gets the arguments `(code, output)`. +//@ Executes the given `command` _synchronously_, unless otherwise specified. When in synchronous +//@ mode returns the object `{ code:..., stdout:... , stderr:... }`, containing the program's +//@ `stdout`, `stderr`, and its exit `code`. Otherwise returns the child process object, +//@ and the `callback` gets the arguments `(code, stdout, stderr)`. //@ //@ **Note:** For long-lived processes, it's best to run `exec()` asynchronously as //@ the current synchronous implementation uses a lot of CPU. This should be getting diff --git a/test/cp.js b/test/cp.js index 42cb095..e8d2b13 100644 --- a/test/cp.js +++ b/test/cp.js @@ -1,14 +1,11 @@ var shell = require('..'); var assert = require('assert'), - fs = require('fs'); + fs = require('fs'), + numLines = require('./utils/utils').numLines; shell.config.silent = true; -function numLines(str) { - return typeof str === 'string' ? str.match(/\n/g).length : 0; -} - shell.rm('-rf', 'tmp'); shell.mkdir('tmp'); diff --git a/test/exec.js b/test/exec.js index 1ea3be4..604e30f 100644 --- a/test/exec.js +++ b/test/exec.js @@ -44,19 +44,21 @@ process.exit = old_exit; var result = shell.exec('node -e \"console.log(1234);\"'); assert.equal(shell.error(), null); assert.equal(result.code, 0); -assert.ok(result.output === '1234\n' || result.output === '1234\nundefined\n'); // 'undefined' for v0.4 +assert.ok(result.stdout === '1234\n' || result.stdout === '1234\nundefined\n'); // 'undefined' for v0.4 // check if stderr goes to output var result = shell.exec('node -e \"console.error(1234);\"'); assert.equal(shell.error(), null); assert.equal(result.code, 0); -assert.ok(result.output === '1234\n' || result.output === '1234\nundefined\n'); // 'undefined' for v0.4 +assert.ok(result.stdout === '' || result.stdout === 'undefined\n'); // 'undefined' for v0.4 +assert.ok(result.stderr === '1234\n' || result.stderr === '1234\nundefined\n'); // 'undefined' for v0.4 // check if stdout + stderr go to output var result = shell.exec('node -e \"console.error(1234); console.log(666);\"'); assert.equal(shell.error(), null); assert.equal(result.code, 0); -assert.ok(result.output === '1234\n666\n' || result.output === '1234\n666\nundefined\n'); // 'undefined' for v0.4 +assert.ok(result.stdout === '666\n' || result.stdout === '666\nundefined\n'); // 'undefined' for v0.4 +assert.ok(result.stderr === '1234\n' || result.stderr === '1234\nundefined\n'); // 'undefined' for v0.4 // check exit code var result = shell.exec('node -e \"process.exit(12);\"'); @@ -68,14 +70,14 @@ shell.cd('resources/external'); var result = shell.exec('node node_script.js'); assert.equal(shell.error(), null); assert.equal(result.code, 0); -assert.equal(result.output, 'node_script_1234\n'); +assert.equal(result.stdout, 'node_script_1234\n'); shell.cd('../..'); // check quotes escaping var result = shell.exec( util.format('node -e "console.log(%s);"', "\\\"\\'+\\'_\\'+\\'\\\"") ); assert.equal(shell.error(), null); assert.equal(result.code, 0); -assert.equal(result.output, "'+'_'+'\n"); +assert.equal(result.stdout, "'+'_'+'\n"); // // async @@ -89,23 +91,26 @@ assert.ok('stdout' in c, 'async exec returns child process object'); // // callback as 2nd argument // -shell.exec('node -e \"console.log(5678);\"', function(code, output) { +shell.exec('node -e \"console.log(5678);\"', function(code, stdout, stderr) { assert.equal(code, 0); - assert.ok(output === '5678\n' || output === '5678\nundefined\n'); // 'undefined' for v0.4 + assert.ok(stdout === '5678\n' || stdout === '5678\nundefined\n'); // 'undefined' for v0.4 + assert.ok(stderr === '' || stderr === 'undefined\n'); // 'undefined' for v0.4 // // callback as 3rd argument // - shell.exec('node -e \"console.log(5566);\"', {async:true}, function(code, output) { + shell.exec('node -e \"console.log(5566);\"', {async:true}, function(code, stdout, stderr) { assert.equal(code, 0); - assert.ok(output === '5566\n' || output === '5566\nundefined\n'); // 'undefined' for v0.4 + assert.ok(stdout === '5566\n' || stdout === '5566\nundefined\n'); // 'undefined' for v0.4 + assert.ok(stderr === '' || stderr === 'undefined\n'); // 'undefined' for v0.4 // // callback as 3rd argument (slient:true) // - shell.exec('node -e \"console.log(5678);\"', {silent:true}, function(code, output) { + shell.exec('node -e \"console.log(5678);\"', {silent:true}, function(code, stdout, stderr) { assert.equal(code, 0); - assert.ok(output === '5678\n' || output === '5678\nundefined\n'); // 'undefined' for v0.4 + assert.ok(stdout === '5678\n' || stdout === '5678\nundefined\n'); // 'undefined' for v0.4 + assert.ok(stderr === '' || stderr === 'undefined\n'); // 'undefined' for v0.4 shell.exit(123); diff --git a/test/mkdir.js b/test/mkdir.js index 8d0896b..0ae8e98 100644 --- a/test/mkdir.js +++ b/test/mkdir.js @@ -1,14 +1,11 @@ var shell = require('..'); var assert = require('assert'), - fs = require('fs'); + fs = require('fs'), + numLines = require('./utils/utils').numLines; shell.config.silent = true; -function numLines(str) { - return typeof str === 'string' ? str.match(/\n/g).length : 0; -} - shell.rm('-rf', 'tmp'); shell.mkdir('tmp'); diff --git a/test/mv.js b/test/mv.js index 8c707a0..664d305 100644 --- a/test/mv.js +++ b/test/mv.js @@ -1,14 +1,11 @@ var shell = require('..'); var assert = require('assert'), - fs = require('fs'); + fs = require('fs'), + numLines = require('./utils/utils').numLines; shell.config.silent = true; -function numLines(str) { - return typeof str === 'string' ? str.match(/\n/g).length : 0; -} - shell.rm('-rf', 'tmp'); shell.mkdir('tmp'); diff --git a/test/pushd.js b/test/pushd.js index c1742e0..8c23a0d 100644 --- a/test/pushd.js +++ b/test/pushd.js @@ -183,7 +183,7 @@ assert.deepEqual(trail, [ // Push invalid directory shell.pushd('does/not/exist'); -assert.equal(shell.error(), 'pushd: no such file or directory: ' + path.resolve('.', 'does/not/exist') + '\n'); +assert.equal(shell.error(), 'pushd: no such file or directory: ' + path.resolve('.', 'does/not/exist')); assert.equal(process.cwd(), trail[0]); // Push without arguments should swap top two directories when stack length is 2 @@ -219,6 +219,6 @@ assert.equal(process.cwd(), trail[0]); // Push without arguments invalid when stack is empty reset(); shell.pushd(); -assert.equal(shell.error(), 'pushd: no other directory\n'); +assert.equal(shell.error(), 'pushd: no other directory'); -shell.exit(123); \ No newline at end of file +shell.exit(123); diff --git a/test/utils/utils.js b/test/utils/utils.js new file mode 100644 index 0000000..98bd8a2 --- /dev/null +++ b/test/utils/utils.js @@ -0,0 +1,5 @@ +function _numLines(str) { + return typeof str === 'string' ? (str.match(/\n/g)||[]).length+1 : 0; +} + +exports.numLines = _numLines;