Wykonanie polecenia powłoki node.js.

113

Nadal próbuję uchwycić szczegóły, w jaki sposób mogę uruchomić polecenie powłoki systemu Linux lub Windows i przechwytywać dane wyjściowe w node.js; ostatecznie chcę zrobić coś takiego ...

//pseudocode
output = run_command(cmd, args)

Ważnym elementem jest to, że outputmusi być dostępny dla zmiennej (lub obiektu) o zasięgu globalnym. Wypróbowałem następującą funkcję, ale z jakiegoś powodu wyświetla się undefinedna konsoli ...

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

Mam problem ze zrozumieniem, gdzie powyższy kod się psuje ... działa bardzo prosty prototyp tego modelu ...

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

Czy ktoś może mi pomóc zrozumieć, dlaczego try_this()działa, a run_cmd()nie? FWIW, muszę użyć child_process.spawn, ponieważ child_process.execma limit bufora 200KB.

Ostateczna rozdzielczość

Przyjmuję odpowiedź Jamesa White'a, ale to jest dokładny kod, który zadziałał dla mnie ...

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
źródło
2
W rezolucji Końcowego należy ustawić me.stdout = "";w cmd_exec()celu zapobieżenia złączenie undefinedna początku związku.
aorcsik
Hej, ten ostateczny kod rozdzielczości jest kompletnie okropny, a co jeśli wykonanie twojego netstata zajmie więcej niż 0,25 sekundy?
Steven Lu
Ummmm ... może skorzystaj z jednej z odpowiedzi, dla której przyznałem bonus ?????
Mike Pennington,
Możliwy duplikat polecenia Wykonaj i uzyskaj dane wyjściowe polecenia powłoki w node.js
Damjan Pavlica

Odpowiedzi:

89

Istnieją trzy problemy, które należy naprawić:

Po pierwsze , oczekujesz zachowania synchronicznego podczas asynchronicznego używania standardowego wyjścia. Wszystkie wywołania w twojej run_cmdfunkcji są asynchroniczne, więc spowoduje to powstanie procesu potomnego i natychmiastowe zwrócenie, niezależnie od tego, czy niektóre, wszystkie czy żadne dane nie zostały odczytane ze standardowego wyjścia. Jako takie, kiedy biegasz

console.log(foo.stdout);

dostajesz wszystko, co jest obecnie przechowywane w foo.stdout i nie ma gwarancji, co to będzie, ponieważ Twój proces potomny może nadal działać.

Po drugie , stdout jest strumieniem , który można odczytać , więc 1) zdarzenie danych można wywołać wiele razy, a 2) wywołanie zwrotne otrzymuje bufor, a nie łańcuch. Łatwe do naprawienia; po prostu zmień

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

w

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

więc konwertujemy nasz bufor na łańcuch i dodajemy ten ciąg do naszej zmiennej stdout.

Po trzecie , możesz wiedzieć, że otrzymałeś wszystkie dane wyjściowe tylko wtedy, gdy otrzymasz zdarzenie 'end', co oznacza, że ​​potrzebujemy innego nasłuchiwania i wywołania zwrotnego:

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

Tak więc ostateczny wynik jest następujący:

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
źródło
„nie ma gwarancji, co to będzie, ponieważ Twój proces podrzędny może nadal działać” zamknij ... ale istnieje gwarancja, że nie zostanie ustawiony w tym momencie i zostanie ustawiony tylko wtedy, gdy wywołanie zwrotne zostanie ostatecznie wywołane, jak wskazałeś w innym miejscu
4
To doskonała odpowiedź ze świetnymi wyjaśnieniami kilku bardzo ważnych koncepcji JS. Miły!
L0j1k
1
Będziesz chciał zrobić to this.stdout = "";wewnątrz run()funkcji, w przeciwnym razie twój console.log(foo.sdtout);będzie poprzedzony przedrostkiem undefined.
f1lt3r
78

Uproszczona wersja zaakceptowanej odpowiedzi (trzeci punkt) po prostu zadziałała.

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

Stosowanie:

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

run_cmd( "hostname", [], function(text) { console.log (text) });
cibercitizen1
źródło
1
A co z wartością zwracaną, kiedy proces zwraca wartość różną od zera, jak mogę ją uzyskać?
Vitim.us
2
child.stdout.on('close', (errCode) => { console.log(errCode) } )
Tushar Gautam
52

Użyłem tego bardziej zwięźle:

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

działa idealnie. :)

Mimouni
źródło
1
Działa to dobrze i nie wymaga żadnych dodatkowych modułów węzłów. Lubię to!
bearvarine
9
sys.puts()został wycofany w 2011 roku (z Node.js v0.2.3). Zamiast tego powinieneś użyć console.log().
tfmontague
1
to faktycznie działa ... więc zastanawiam się, dlaczego to nie jest odpowiedź? to takie proste
ekkis
Łał!! ta odpowiedź jest doskonała i elegancka. Dziękuję Ci.
Em Ji Madhu
43

Najprostszym sposobem jest użycie biblioteki ShellJS ...

$ npm install [-g] shelljs

Przykład EXEC:

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 obsługuje wiele popularnych poleceń powłoki odwzorowanych jako funkcje NodeJS, w tym:

  • kot
  • Płyta CD
  • chmod
  • cp
  • reż
  • Echo
  • exec
  • wyjście
  • odnaleźć
  • grep
  • ln
  • ls
  • mkdir
  • mv
  • popd
  • pushd
  • pwd
  • rm
  • sed
  • test
  • który
Tony O'Hagan
źródło
jak dodać parametr do skryptu powłoki wywoływanego przez shell.exec ("foo.sh")?
pseudozach
1
Można po prostu dołączyć twoje argumenty góry napis: shell.exec("foo.sh arg1 arg2 ... "). Twój foo.shskrypt może odwołać je za pomocą $1, $2... itd.
Tony O'Hagan
Jeśli polecenie, które próbujesz wykonać, będzie wymagało danych wejściowych od użytkownika, NIE używaj ShellJS exec (). Ta funkcja nie ma charakteru interaktywnego, ponieważ pobierze polecenie i wydrukuje dane wyjściowe, nie może przyjmować danych wejściowych pomiędzy. Zamiast tego użyj wbudowanego procesu child_process. Np. Https://stackoverflow.com/a/31104898/9749509
MPatel1
4

Miałem podobny problem i napisałem do tego rozszerzenie węzła. Możesz sprawdzić repozytorium git. To open source, darmowe i wszystkie dobre rzeczy!

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

ExecXI to rozszerzenie węzła napisane w C ++ w celu wykonywania poleceń powłoki jeden po drugim, wysyłając dane wyjściowe polecenia do konsoli w czasie rzeczywistym. Obecne są opcjonalne łańcuchowe i niezwiązane sposoby; co oznacza, że ​​możesz zatrzymać skrypt po niepowodzeniu polecenia (połączonym w łańcuch) lub możesz kontynuować, jakby nic się nie stało!

Instrukcje użytkowania znajdują się w pliku ReadMe . Zapraszam do składania żądań ściągnięcia lub zgłaszania problemów!

Pomyślałem, że warto o tym wspomnieć.

Logan
źródło
4

@ TonyO'Hagan to kompleksowa shelljsodpowiedź, ale chciałbym podkreślić synchroniczną wersję jego odpowiedzi:

var shell = require('shelljs');
var output = shell.exec('netstat -rn', {silent:true}).output;
console.log(output);
Stephen Quan
źródło
1

Synchroniczna jednowarstwowa:

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

Victorio Berra
źródło
0

W Twojej run_cmdfunkcji występuje konflikt zmiennych :

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

Po prostu zmień to na to:

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

Aby zobaczyć błędy, zawsze dodaj to:

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

EDYCJA Twój kod nie powiódł się, ponieważ próbujesz uruchomić, dirco nie jest dostarczane jako oddzielny program autonomiczny. To polecenie w cmdtoku. Jeśli chcesz grać z systemem plików, użyj natywnego require( 'fs' ).

Alternatywnie (czego nie polecam) możesz utworzyć plik wsadowy, który możesz następnie uruchomić. Zauważ, że system operacyjny domyślnie uruchamia pliki wsadowe za pośrednictwem cmd.

kapryśny
źródło
dziękuję za pomoc ... jednak nawet kiedy uruchamiam C:\Windows\System32\netstat.exe, to nadal nie daje rezultatów ... Moja dokładna składnia była foo = new run_cmd('netstat.exe', ['-an'], function (me, data){me.stdout=data;});... Próbowałem też pełnej ścieżki bez powodzenia
Mike Pennington
0

W rzeczywistości nie zwracasz niczego z funkcji run_cmd.

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

Działa dobrze. :)

Chris Eineke
źródło
Myślę, że nie returnjest to wymagane, o ile poprawnie ustawisz atrybuty obiektu
Mike Pennington
0

Obiecana wersja najczęściej nagradzanej odpowiedzi:

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

Używać:

 runCmd('ls').then(ret => console.log(ret))
Szczery
źródło