node.js shell command execution
Asked Answered
G

10

117

I am still trying to grasp the finer points of how I can run a linux or windows shell command and capture output within node.js; ultimately, I want to do something like this...

//pseudocode
output = run_command(cmd, args)

The important piece is that output must be available to a globally scoped variable (or object). I tried the following function, but for some reason, I get undefined printed to the console...

function run_cmd(cmd, args, cb) {
  var spawn = require('child_process').spawn
  var child = spawn(cmd, args);
  var me = this;
  child.stdout.on('data', function(me, data) {
    cb(me, data);
  });
}
foo = new run_cmd('dir', ['/B'], function (me, data){me.stdout=data;});
console.log(foo.stdout);  // yields "undefined" <------

I'm having trouble understanding where the code breaks above... a very simple prototype of that model works...

function try_this(cmd, cb) {
  var me = this;
  cb(me, cmd)
}
bar = new try_this('guacamole', function (me, cmd){me.output=cmd;})
console.log(bar.output); // yields "guacamole" <----

Can someone help me understand why try_this() works, and run_cmd() does not? FWIW, I need to use child_process.spawn, because child_process.exec has a 200KB buffer limit.

Final Resolution

I'm accepting James White's answer, but this is the exact code that worked for me...

function cmd_exec(cmd, args, cb_stdout, cb_end) {
  var spawn = require('child_process').spawn,
    child = spawn(cmd, args),
    me = this;
  me.exit = 0;  // Send a cb to set 1 when cmd exits
  me.stdout = "";
  child.stdout.on('data', function (data) { cb_stdout(me, data) });
  child.stdout.on('end', function () { cb_end(me) });
}
foo = new cmd_exec('netstat', ['-rn'], 
  function (me, data) {me.stdout += data.toString();},
  function (me) {me.exit = 1;}
);
function log_console() {
  console.log(foo.stdout);
}
setTimeout(
  // wait 0.25 seconds and print the output
  log_console,
250);
Greatgrandaunt answered 22/1, 2013 at 12:25 Comment(4)
In the Final Resolution you should set me.stdout = ""; in cmd_exec() to prevent concatenating undefined to the beginning of the result.Skuld
Hey, that final resolution code is completely awful, what if it takes longer than 0.25 second to execute your netstat?Dawkins
Ummmm... maybe use one of the answers that I awarded a bonus to?????Greatgrandaunt
Possible duplicate of Execute and get the output of a shell command in node.jsRecollected
P
89

There are three issues here that need to be fixed:

First is that you are expecting synchronous behavior while using stdout asynchronously. All of the calls in your run_cmd function are asynchronous, so it will spawn the child process and return immediately regardless of whether some, all, or none of the data has been read off of stdout. As such, when you run

console.log(foo.stdout);

you get whatever happens to be stored in foo.stdout at the moment, and there's no guarantee what that will be because your child process might still be running.

Second is that stdout is a readable stream, so 1) the data event can be called multiple times, and 2) the callback is given a buffer, not a string. Easy to remedy; just change

foo = new run_cmd(
    'netstat.exe', ['-an'], function (me, data){me.stdout=data;}
);

into

foo = new run_cmd(
    'netstat.exe', ['-an'], function (me, buffer){me.stdout+=buffer.toString();}
);

so that we convert our buffer into a string and append that string to our stdout variable.

Third is that you can only know you've received all output when you get the 'end' event, which means we need another listener and callback:

function run_cmd(cmd, args, cb, end) {
    // ...
    child.stdout.on('end', end);
}

So, your final result is this:

function run_cmd(cmd, args, cb, end) {
    var spawn = require('child_process').spawn,
        child = spawn(cmd, args),
        me = this;
    child.stdout.on('data', function (buffer) { cb(me, buffer) });
    child.stdout.on('end', end);
}

// Run C:\Windows\System32\netstat.exe -an
var foo = new run_cmd(
    'netstat.exe', ['-an'],
    function (me, buffer) { me.stdout += buffer.toString() },
    function () { console.log(foo.stdout) }
);
Pathological answered 24/1, 2013 at 21:6 Comment(3)
"there's no guarantee what that will be because your child process might still be running" close...but there is a guarantee that it will not be set at that point in time, and will only be set when the callback is finally called, as you indicated elsewhereClaim
This is an excellent answer with great explanations of some very important JS concepts. Nice!Darill
You'll want to do this.stdout = ""; inside the run() function, otherwise your console.log(foo.sdtout); will be prefixed with undefined.Abana
P
79

A simplified version of the accepted answer (third point), just worked for me.

function run_cmd(cmd, args, callBack ) {
    var spawn = require('child_process').spawn;
    var child = spawn(cmd, args);
    var resp = "";

    child.stdout.on('data', function (buffer) { resp += buffer.toString() });
    child.stdout.on('end', function() { callBack (resp) });
} // ()

Usage:

run_cmd( "ls", ["-l"], function(text) { console.log (text) });

run_cmd( "hostname", [], function(text) { console.log (text) });
Pleuron answered 4/7, 2013 at 9:16 Comment(2)
What about the return value, when the process return a non zero, how can I get it?Truculent
child.stdout.on('close', (errCode) => { console.log(errCode) } )Monroe
B
52

I used this more concisely :

var sys = require('sys')
var exec = require('child_process').exec;
function puts(error, stdout, stderr) { sys.puts(stdout) }
exec("ls -la", puts);

it works perfectly. :)

Bate answered 18/4, 2014 at 14:40 Comment(4)
This works well, and does not require any additional node modules. I like it!Printery
sys.puts() was deprecated in 2011 (with Node.js v0.2.3). You should use console.log() instead.Zadoc
that actually works... so I'm wondering, why isn't this the answer? it's so simpleAutumnautumnal
Wow!! this answer is perfect and elegant. thank you.Bihar
L
45

Simplest way is to just use the ShellJS lib ...

$ npm install [-g] shelljs

EXEC Example:

require('shelljs/global');

// Sync call to exec()
var version = exec('node --version', {silent:true}).output;

// Async call to exec()
exec('netstat.exe -an', function(status, output) {
  console.log('Exit status:', status);
  console.log('Program output:', output);
});

ShellJs.org supports many common shell commands mapped as NodeJS functions including:

  • cat
  • cd
  • chmod
  • cp
  • dirs
  • echo
  • exec
  • exit
  • find
  • grep
  • ln
  • ls
  • mkdir
  • mv
  • popd
  • pushd
  • pwd
  • rm
  • sed
  • test
  • which
Lietuva answered 16/7, 2014 at 9:28 Comment(3)
how to add a parameter to the shell script called by shell.exec("foo.sh") ?Scotism
You can just append your arguments top the string: shell.exec("foo.sh arg1 arg2 ... "). Your foo.sh script can reference these using $1, $2 ... etc.Massive
If the command you're trying to execute is going to need input from user then DON'T use ShellJS exec(). This function is not interactive in nature, as it'll just take command and print output, Can't accept inputs in between. Use built in child_process instead. E.g. https://mcmap.net/q/107528/-use-child_process-execsync-but-keep-output-in-consoleMenstrual
E
4

I had a similar problem and I ended up writing a node extension for this. You can check out the git repository. It's open source and free and all that good stuff !

https://github.com/aponxi/npm-execxi

ExecXI is a node extension written in C++ to execute shell commands one by one, outputting the command's output to the console in real-time. Optional chained, and unchained ways are present; meaning that you can choose to stop the script after a command fails (chained), or you can continue as if nothing has happened !

Usage instructions are in the ReadMe file. Feel free to make pull requests or submit issues!

I thought it was worth to mention it.

Empson answered 28/2, 2013 at 7:31 Comment(0)
A
4

@TonyO'Hagan is comprehrensive shelljs answer, but, I would like to highlight the synchronous version of his answer:

var shell = require('shelljs');
var output = shell.exec('netstat -rn', {silent:true}).output;
console.log(output);
Auctioneer answered 23/9, 2016 at 0:41 Comment(0)
N
2

Synchronous one-liner:

require('child_process').execSync("echo 'hi'", function puts(error, stdout, stderr) {
  console.log(stdout) 
});
Niacin answered 27/1, 2017 at 19:34 Comment(0)
G
0

There's a variable conflict in your run_cmd function:

  var me = this;
  child.stdout.on('data', function(me, data) {
    // me is overriden by function argument
    cb(me, data);
  });

Simply change it to this:

  var me = this;
  child.stdout.on('data', function(data) {
    // One argument only!
    cb(me, data);
  });

In order to see errors always add this:

  child.stderr.on('data', function(data) {
      console.log( data );
  });

EDIT You're code fails because you are trying to run dir which is not provided as a separate standalone program. It is a command in cmd process. If you want to play with filesystem use native require( 'fs' ).

Alternatively ( which I do not recommend ) you can create a batch file which you can then run. Note that OS by default fires batch files via cmd.

Gargoyle answered 22/1, 2013 at 13:3 Comment(1)
thank you for your help... however, even when I run C:\Windows\System32\netstat.exe, this still does not yield results... My exact syntax was foo = new run_cmd('netstat.exe', ['-an'], function (me, data){me.stdout=data;});... I also tried the full path with no success so farGreatgrandaunt
S
0

You're not actually returning anything from your run_cmd function.

function run_cmd(cmd, args, done) {
    var spawn = require("child_process").spawn;
    var child = spawn(cmd, args);
    var result = { stdout: "" };
    child.stdout.on("data", function (data) {
            result.stdout += data;
    });
    child.stdout.on("end", function () {
            done();
    });
    return result;
}

> foo = run_cmd("ls", ["-al"], function () { console.log("done!"); });
{ stdout: '' }
done!
> foo.stdout
'total 28520...'

Works just fine. :)

Sacco answered 25/1, 2013 at 3:11 Comment(1)
I don't think return is required as long as you properly set the object attributesGreatgrandaunt
T
0

A promisified version of the most-awarded answer:

  runCmd: (cmd, args) => {
    return new Promise((resolve, reject) => {
      var spawn = require('child_process').spawn
      var child = spawn(cmd, args)
      var resp = ''
      child.stdout.on('data', function (buffer) { resp += buffer.toString() })
      child.stdout.on('end', function () { resolve(resp) })
    })
  }

To use:

 runCmd('ls').then(ret => console.log(ret))
Thence answered 10/2, 2018 at 6:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.