Ausführung des Shell-Befehls node.js.

113

Ich versuche immer noch, die Feinheiten zu erfassen, wie ich einen Linux- oder Windows-Shell-Befehl ausführen und die Ausgabe in node.js erfassen kann. letztendlich möchte ich so etwas machen ...

//pseudocode
output = run_command(cmd, args)

Das wichtige outputElement ist, dass es für eine Variable (oder ein Objekt) mit globalem Gültigkeitsbereich verfügbar sein muss. Ich habe die folgende Funktion ausprobiert, aber aus irgendeinem Grund werde ich undefinedauf der Konsole gedruckt ...

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" <------

Ich habe Probleme zu verstehen, wo der Code oben kaputt geht ... ein sehr einfacher Prototyp dieses Modells funktioniert ...

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" <----

Kann mir jemand helfen zu verstehen, warum try_this()funktioniert und run_cmd()nicht? FWIW muss ich verwenden child_process.spawn, da child_process.execes ein Pufferlimit von 200 KB gibt.

Endgültige Lösung

Ich akzeptiere James Whites Antwort, aber das ist genau der Code, der für mich funktioniert hat ...

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);
Mike Pennington
quelle
2
Im Final Resolution sollten Sie setzen me.stdout = "";in cmd_exec()verhindern verketten undefinedzu Beginn des Ergebnisses.
Aorcsik
Hey, dieser endgültige Auflösungscode ist völlig schrecklich. Was ist, wenn die Ausführung Ihres netstat länger als 0,25 Sekunden dauert?
Steven Lu
Ummmm ... benutze vielleicht eine der Antworten, denen ich einen Bonus gegeben habe ?????
Mike Pennington
Mögliches Duplikat von Ausführen und die Ausgabe eines Shell-Befehls in node.js
Damjan Pavlica

Antworten:

89

Hier müssen drei Probleme behoben werden:

Erstens erwarten Sie ein synchrones Verhalten, wenn Sie stdout asynchron verwenden. Alle Aufrufe in Ihrer run_cmdFunktion sind asynchron, sodass der untergeordnete Prozess ausgelöst und sofort zurückgegeben wird, unabhängig davon, ob einige, alle oder keine der Daten von stdout ausgelesen wurden. Als solches, wenn Sie laufen

console.log(foo.stdout);

Sie erhalten alles, was gerade in foo.stdout gespeichert ist, und es gibt keine Garantie dafür, was das sein wird, da Ihr untergeordneter Prozess möglicherweise noch ausgeführt wird.

Zweitens ist stdout ein lesbarer Stream . 1) Das Datenereignis kann mehrmals aufgerufen werden, und 2) der Rückruf erhält einen Puffer und keine Zeichenfolge. Leicht zu beheben; nur ändern

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

in

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

Damit konvertieren wir unseren Puffer in eine Zeichenfolge und hängen diese Zeichenfolge an unsere stdout-Variable an.

Drittens können Sie nur dann wissen, dass Sie alle Ausgaben erhalten haben, wenn Sie das End-Ereignis erhalten. Dies bedeutet, dass wir einen weiteren Listener und Rückruf benötigen:

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

Ihr Endergebnis lautet also:

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) }
);
James White
quelle
"Es gibt keine Garantie dafür, was das sein wird, weil Ihr untergeordneter Prozess möglicherweise noch ausgeführt wird." Schließen Sie ... aber es gibt eine Garantie dafür, dass er zu diesem Zeitpunkt nicht festgelegt wird und erst festgelegt wird, wenn der Rückruf endgültig aufgerufen wird. wie Sie an anderer Stelle angegeben
4
Dies ist eine hervorragende Antwort mit großartigen Erklärungen einiger sehr wichtiger JS-Konzepte. Nett!
L0j1k
1
Sie möchten dies this.stdout = "";innerhalb der run()Funktion tun , andernfalls wird Ihrem console.log(foo.sdtout);ein Präfix vorangestellt undefined.
f1lt3r
78

Eine vereinfachte Version der akzeptierten Antwort (dritter Punkt) hat gerade für mich funktioniert.

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) });
} // ()

Verwendung:

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

run_cmd( "hostname", [], function(text) { console.log (text) });
cibercitizen1
quelle
1
Was ist mit dem Rückgabewert, wenn der Prozess eine Nicht-Null zurückgibt, wie kann ich ihn erhalten?
Vitim.us
2
child.stdout.on('close', (errCode) => { console.log(errCode) } )
Tushar Gautam
52

Ich habe das prägnanter verwendet:

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

es funktioniert perfekt. :) :)

Mimouni
quelle
1
Dies funktioniert gut und erfordert keine zusätzlichen Knotenmodule. Ich mag das!
Bearvarine
9
sys.puts()wurde 2011 veraltet (mit Node.js v0.2.3). Sie sollten console.log()stattdessen verwenden.
Montag,
1
das funktioniert tatsächlich ... also frage ich mich, warum ist das nicht die Antwort? es ist so einfach
ekkis
Beeindruckend!! Diese Antwort ist perfekt und elegant. Danke.
Em Ji Madhu
43

Am einfachsten ist es, einfach die ShellJS-Bibliothek zu verwenden ...

$ npm install [-g] shelljs

EXEC Beispiel:

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 unterstützt viele gängige Shell-Befehle, die als NodeJS-Funktionen zugeordnet sind, darunter:

  • Katze
  • CD
  • chmod
  • vgl
  • dirs
  • Echo
  • exec
  • Ausfahrt
  • finden
  • grep
  • ln
  • ls
  • mkdir
  • mv
  • popd
  • pushd
  • pwd
  • rm
  • sed
  • Prüfung
  • welche
Tony O'Hagan
quelle
Wie füge ich dem von shell.exec ("foo.sh") aufgerufenen Shell-Skript einen Parameter hinzu?
Pseudozach
1
Sie können Ihre Argumente einfach an die Zeichenfolge anhängen : shell.exec("foo.sh arg1 arg2 ... "). Ihr foo.shSkript kann diese mit Referenz $1, $2... etc.
Tony O'Hagan
Wenn der Befehl, den Sie ausführen möchten, vom Benutzer eingegeben werden muss, verwenden Sie ShellJS exec () NICHT. Diese Funktion ist nicht interaktiv, da sie nur Befehle und Druckausgaben übernimmt. Dazwischen können keine Eingaben akzeptiert werden. Verwenden Sie stattdessen den integrierten child_process. ZB https://stackoverflow.com/a/31104898/9749509
MPatel1
4

Ich hatte ein ähnliches Problem und schrieb schließlich eine Knotenerweiterung dafür. Sie können das Git-Repository auschecken. Es ist Open Source und kostenlos und all das gute Zeug!

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

ExecXI ist eine in C ++ geschriebene Knotenerweiterung, mit der Shell-Befehle einzeln ausgeführt werden und die Ausgabe des Befehls in Echtzeit an die Konsole ausgegeben wird. Optional sind verkettete und nicht verkettete Wege vorhanden. Dies bedeutet, dass Sie das Skript stoppen können, nachdem ein Befehl fehlgeschlagen (verkettet) ist, oder Sie können fortfahren, als wäre nichts passiert!

Gebrauchsanweisungen finden Sie in der ReadMe-Datei . Fühlen Sie sich frei, Pull-Anfragen zu stellen oder Probleme einzureichen!

Ich fand es wert, es zu erwähnen.

Logan
quelle
4

@ TonyO'Hagan ist eine umfassende shelljsAntwort, aber ich möchte die synchrone Version seiner Antwort hervorheben:

var shell = require('shelljs');
var output = shell.exec('netstat -rn', {silent:true}).output;
console.log(output);
Stephen Quan
quelle
1

Synchroner Einzeiler:

require('child_process').execSync("echo 'hi'", function puts(error, stdout, stderr) { console.log(stdout) });

Victorio Berra
quelle
0

Es gibt einen variablen Konflikt in Ihrer run_cmdFunktion:

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

Ändern Sie es einfach in Folgendes:

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

Um Fehler zu sehen, fügen Sie immer Folgendes hinzu:

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

BEARBEITEN Ihr Code schlägt fehl, weil Sie versuchen, ihn auszuführen, dirder nicht als separates eigenständiges Programm bereitgestellt wird. Es ist ein Befehl in cmdBearbeitung. Wenn Sie mit dem Dateisystem spielen möchten, verwenden Sie native require( 'fs' ).

Alternativ (was ich nicht empfehle) können Sie eine Batch-Datei erstellen, die Sie dann ausführen können. Beachten Sie, dass das Betriebssystem standardmäßig Batchdateien über auslöst cmd.

verrückt
quelle
Vielen Dank für Ihre Hilfe ... aber selbst wenn ich laufe C:\Windows\System32\netstat.exe, liefert dies immer noch keine Ergebnisse ... Meine genaue Syntax war foo = new run_cmd('netstat.exe', ['-an'], function (me, data){me.stdout=data;});... Ich habe auch den vollständigen Weg bisher ohne Erfolg ausprobiert
Mike Pennington
0

Sie geben tatsächlich nichts von Ihrer Funktion run_cmd zurück.

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...'

Funktioniert gut. :) :)

Chris Eineke
quelle
Ich denke nicht, dass dies returnerforderlich ist, solange Sie die Objektattribute richtig eingestellt haben
Mike Pennington
0

Eine versprochene Version der am meisten ausgezeichneten Antwort:

  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) })
    })
  }

Benutzen:

 runCmd('ls').then(ret => console.log(ret))
Frank
quelle