56

The code snippet shown below works great for obtaining access to the stdout of a system command. Is there some way that I can modify this code so as to also get access to the exit code of the system command and any output that the system command sent to stderr?

#!/usr/bin/node
var child_process = require('child_process');
function systemSync(cmd) {
  return child_process.execSync(cmd).toString();
};
console.log(systemSync('pwd'));
Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
user3311045
  • 689
  • 1
  • 7
  • 15

3 Answers3

92

You do not need to do it Async. You can keep your execSync function.

Wrap it in a try, and the Error passed to the catch(e) block will contain all the information you're looking for.

var child_process = require('child_process');

function systemSync(cmd) {
  try {
    return child_process.execSync(cmd).toString();
  } 
  catch (error) {
    error.status;  // Might be 127 in your example.
    error.message; // Holds the message you typically want.
    error.stderr;  // Holds the stderr output. Use `.toString()`.
    error.stdout;  // Holds the stdout output. Use `.toString()`.
  }
};

console.log(systemSync('pwd'));

If an error is NOT thrown, then:

  • status is guaranteed to be 0
  • stdout is what's returned by the function
  • stderr is almost definitely empty because it was successful.

In the rare event the command line executable returns a stderr and yet exits with status 0 (success), and you want to read it, you will need the async function.

lance.dolan
  • 3,493
  • 27
  • 36
  • 2
    Thanks lance - created this npm module - shelljs.exec - which wraps all the logic you've laid down here into a nice little packet - cheers :D – danday74 Sep 19 '17 at 21:35
  • 5
    I would say that it's actually common for processes to return an exit code of 0 yet also include output in stderr. stderr is quite often used for debugging information and other info not directly related to command output. – rumdrums May 06 '18 at 21:00
  • @IshaanGandhi if that's true, I'd like to confirm what version the behavior changed in. I also wonder if there is some configuration that can be passed to enable this expected behavior. I'm personally not working on any node projects anymore in order to confirm this myself – lance.dolan Apr 06 '20 at 23:08
  • 1
    My mistake. I think it should work in general. When I run the command in electron using node 8, no error is thrown, but one is thrown from the node repl. Perhaps there is a bug in electron. – YOLT Apr 07 '20 at 18:17
  • One thing to note is that **if the parent process kills the child if maxBuffer is exceeded**, execSync **will throw an error** but the `error.status will be null` ! – human Oct 15 '20 at 11:48
  • I use this code and works as expected, but without the function (I run the snippet directly). There is one issue though: When `error.stderr` is not empty, its content is output to stdout (console, where Node JS outputs its log) when `child_process.execSync(cmd)`. Is there a way to remove that from output? I’d like to output the errors manually (as info messages). Thanks. – tukusejssirs Jan 25 '21 at 16:14
  • I wonder if my case is special because this approach just doesn't work. I'm specifically executing a bash script inside a function. e.g. const myFunc = () => { let stdout = ''; try { const res = execSync('bash scripts/myScript.sh myArgs'); stdout = res.toString(); } catch (error) { console.log(error.body.message); console.log(error); throw new Error(error); } ...etc... }; and I never see anything logged. The error just throws. Is it the bash script execution? is it that I'm throwing the error afterwards? Still experimenting... – Methodician Aug 07 '23 at 23:16
24

You will want the async/callback version of exec. There are 3 values returned. The last two are stdout and stderr. Also, child_process is an event emitter. Listen for the exit event. The first element of the callback is the exit code. (Obvious from the syntax, you'll want to use node 4.1.1 to get the code below to work as written)

const child_process = require("child_process")
function systemSync(cmd){
  child_process.exec(cmd, (err, stdout, stderr) => {
    console.log('stdout is:' + stdout)
    console.log('stderr is:' + stderr)
    console.log('error is:' + err)
  }).on('exit', code => console.log('final exit code is', code))
}

Try the following:

`systemSync('pwd')`

`systemSync('notacommand')`

And you will get:

final exit code is 0
stdout is:/
stderr is:

Followed by:

final exit code is 127
stdout is:
stderr is:/bin/sh: 1: notacommand: not found
bbuckley123
  • 1,879
  • 13
  • 18
  • > You will want the async/callback version of exec. – user3311045 Sep 30 '15 at 21:38
  • I was actually trying to do it synchronously. I have learned that I am apparenntly grasping at straws with that approach. The async approach works great. Thanks for the input. – user3311045 Sep 30 '15 at 21:39
  • @user3311045 - It would be awesome if you accepted my answer. But if you want to hold out for someone who can figure out how to do it with the sync option, that's cool. I tried to figure it out via `execSync`, but I don't think there's a way. – bbuckley123 Sep 30 '15 at 23:29
13

You may also use child_process.spawnSync(), as it returns much more:

return: 
pid <Number> Pid of the child process
output <Array> Array of results from stdio output
stdout <Buffer> | <String> The contents of output[1]
stderr <Buffer> | <String> The contents of output[2]
status <Number> The exit code of the child process
signal <String> The signal used to kill the child process
error <Error> The error object if the child process failed or timed out

So the exit code you're looking for would be ret.status.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
infografnet
  • 3,749
  • 1
  • 35
  • 35