Esecuzione del comando della shell node.js


113

Sto ancora cercando di capire i punti più fini di come posso eseguire un comando di shell Linux o Windows e catturare l'output all'interno di node.js; alla fine, voglio fare qualcosa del genere ...

//pseudocode
output = run_command(cmd, args)

La parte importante è che outputdeve essere disponibile per una variabile (o oggetto) con ambito globale. Ho provato la seguente funzione, ma per qualche motivo vengo undefinedstampato sulla 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" <------

Ho problemi a capire dove si rompe il codice sopra ... un prototipo molto semplice di quel modello funziona ...

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

Qualcuno può aiutarmi a capire perché try_this()funziona e run_cmd()no? FWIW, devo usare child_process.spawn, perché child_process.execha un limite di buffer di 200 KB.

Risoluzione finale

Accetto la risposta di James White, ma questo è il codice esatto che ha funzionato per 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);

2
Nella risoluzione finale è necessario impostare me.stdout = "";in cmd_exec()per evitare concatenare undefinedfino all'inizio del risultato.
aorcsik

Ehi, quel codice di risoluzione finale è completamente orribile, cosa succede se ci vogliono più di 0,25 secondi per eseguire il tuo netstat?
Steven Lu

Ummmm ... magari usi una delle risposte a cui ho assegnato un bonus ?????
Mike Pennington

Risposte:


89

Ci sono tre problemi qui che devono essere risolti:

Il primo è che ti aspetti un comportamento sincrono mentre usi stdout in modo asincrono. Tutte le chiamate nella run_cmdfunzione sono asincrone, quindi genererà il processo figlio e tornerà immediatamente indipendentemente dal fatto che alcuni, tutti o nessuno dei dati siano stati letti dallo stdout. Come tale, quando corri

console.log(foo.stdout);

ottieni tutto ciò che accade al momento archiviato in foo.stdout e non c'è alcuna garanzia di cosa sarà perché il tuo processo figlio potrebbe essere ancora in esecuzione.

Il secondo è che stdout è un flusso leggibile , quindi 1) l'evento dati può essere chiamato più volte e 2) al callback viene assegnato un buffer, non una stringa. Facile da porre rimedio; basta cambiare

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

in modo da convertire il nostro buffer in una stringa e aggiungere quella stringa alla nostra variabile stdout.

Il terzo è che puoi sapere di aver ricevuto tutto l'output solo quando ricevi l'evento 'end', il che significa che abbiamo bisogno di un altro ascoltatore e callback:

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

Quindi, il tuo risultato finale è questo:

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

"non c'è alcuna garanzia che cosa sarà perché il processo figlio potrebbe essere ancora in esecuzione" chiuso ... ma c'è una garanzia che sarà , non è fissato a quel momento, e sarà solo set, quando il callback viene finalmente chiamato, come hai indicato altrove

4
Questa è un'ottima risposta con ottime spiegazioni di alcuni concetti JS molto importanti. Bello!
L0j1k

1
Ti consigliamo di farlo this.stdout = "";all'interno della run()funzione, altrimenti il ​​tuo console.log(foo.sdtout);sarà preceduto da undefined.
f1lt3r

78

Una versione semplificata della risposta accettata (terzo punto), ha funzionato per 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) });
} // ()

Uso:

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

run_cmd( "hostname", [], function(text) { console.log (text) });

1
E il valore restituito, quando il processo restituisce un valore diverso da zero, come posso ottenerlo?
Vitim.us

2
child.stdout.on('close', (errCode) => { console.log(errCode) } )
Tushar Gautam

52

L'ho usato in modo più conciso:

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

funziona perfettamente. :)


1
Funziona bene e non richiede alcun modulo nodo aggiuntivo. Mi piace!
bearvarine

9
sys.puts()è stato deprecato nel 2011 (con Node.js v0.2.3). Dovresti usare console.log()invece.
tfmontague

1
funziona davvero ... quindi mi chiedo, perché non è questa la risposta? è così semplice
ekkis

Wow!! questa risposta è perfetta ed elegante. grazie.
Em Ji Madhu

43

Il modo più semplice è usare semplicemente la libreria ShellJS ...

$ npm install [-g] shelljs

Esempio 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 supporta molti comandi shell comuni mappati come funzioni NodeJS, tra cui:

  • gatto
  • CD
  • chmod
  • cp
  • dirs
  • eco
  • exec
  • Uscita
  • trova
  • grep
  • ln
  • ls
  • mkdir
  • mv
  • popd
  • pushd
  • pwd
  • rm
  • sed
  • test
  • quale

come aggiungere un parametro allo script di shell chiamato da shell.exec ("foo.sh")?
pseudozach

1
Si può solo aggiungere i tuoi argomenti in cima alla stringa: shell.exec("foo.sh arg1 arg2 ... "). Il tuo foo.shscript può fare riferimento a questi usando $1, $2... ecc.
Tony O'Hagan

Se il comando che stai tentando di eseguire necessita dell'input dell'utente, NON usare ShellJS exec (). Questa funzione non è di natura interattiva, in quanto accetta solo il comando e stampa l'output, non può accettare input in mezzo. Usa invece child_process integrato. Ad esempio https://stackoverflow.com/a/31104898/9749509
MPatel1 il

4

Ho avuto un problema simile e ho finito per scrivere un'estensione del nodo per questo. Puoi controllare il repository git. È open source e gratuito e tutte quelle cose buone!

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

ExecXI è un'estensione del nodo scritta in C ++ per eseguire i comandi della shell uno per uno, inviando l'output del comando alla console in tempo reale. Sono presenti modi concatenati e non concatenati opzionali; significa che puoi scegliere di interrompere lo script dopo che un comando fallisce (concatenato), oppure puoi continuare come se non fosse successo niente!

Le istruzioni per l'uso si trovano nel file Leggimi . Sentiti libero di fare richieste pull o inviare problemi!

Ho pensato che valesse la pena menzionarlo.


4

@ TonyO'Hagan è una shelljsrisposta completa, ma vorrei evidenziare la versione sincrona della sua risposta:

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

1

One liner sincrono:

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


0

C'è un conflitto di variabili nella tua run_cmdfunzione:

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

Basta cambiarlo in questo:

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

Per vedere gli errori aggiungi sempre questo:

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

MODIFICA Il tuo codice fallisce perché stai tentando di eseguire il dirche non è fornito come programma autonomo separato. È un comando in cmdcorso. Se vuoi giocare con il filesystem usa native require( 'fs' ).

In alternativa (cosa che non consiglio) puoi creare un file batch che puoi quindi eseguire. Tieni presente che il sistema operativo per impostazione predefinita attiva i file batch tramite cmd.


grazie per il vostro aiuto ... tuttavia, anche quando corro C:\Windows\System32\netstat.exe, questo non dà ancora risultati ... La mia sintassi esatta era foo = new run_cmd('netstat.exe', ['-an'], function (me, data){me.stdout=data;});... Ho anche provato il percorso completo senza successo fino ad ora
Mike Pennington

0

In realtà non stai restituendo nulla dalla tua funzione 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...'

Funziona benissimo. :)


Non penso returnsia necessario fintanto che si impostano correttamente gli attributi dell'oggetto
Mike Pennington

0

Una versione promessa della risposta più premiata:

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

Usare:

 runCmd('ls').then(ret => console.log(ret))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.