web-dev-qa-db-fra.com

node.js Exécution de la commande Shell

J'essaie toujours de comprendre les détails de la manière dont je peux exécuter une commande Linux ou Windows Shell et capturer la sortie dans node.js; en fin de compte, je veux faire quelque chose comme ça ...

//pseudocode
output = run_command(cmd, args)

La pièce importante est que output doit être disponible pour une variable (ou un objet) à portée globale. J'ai essayé la fonction suivante, mais pour une raison quelconque, je reçois undefined imprimé sur la 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" <------

Je n'arrive pas à comprendre où le code casse au-dessus ... un prototype très simple de ce modèle fonctionne ...

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

Quelqu'un peut-il m'aider à comprendre pourquoi try_this() fonctionne, et run_cmd() ne fonctionne pas? FWIW, je dois utiliser child_process.spawn , car child_process.exec a une limite de mémoire tampon de 200 Ko.

Résolution finale

J'accepte la réponse de James White, mais c'est le code exact qui a fonctionné pour moi ...

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);
107
Mike Pennington

Il y a trois problèmes à résoudre:

First signifie que vous attendez un comportement synchrone lorsque vous utilisez stdout de manière asynchrone. Tous les appels de votre fonction run_cmd sont asynchrones. Par conséquent, le processus enfant est généré et est renvoyé immédiatement, que certaines, toutes ou aucune des données aient été lues depuis stdout. En tant que tel, lorsque vous exécutez

console.log(foo.stdout);

vous obtenez tout ce qui se trouve être stocké dans foo.stdout pour le moment, et rien ne garantit ce que ce sera parce que votre processus enfant est peut-être toujours en cours d'exécution.

Second est que stdout est un flux lisible , donc 1) l'événement de données peut être appelé plusieurs fois et 2) le rappel est attribué à un tampon et non à une chaîne. Facile à remédier; il suffit de changer

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

dans

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

afin que nous convertissions notre tampon en chaîne et que nous ajoutions cette chaîne à notre variable stdout.

Third est que vous ne pouvez savoir que vous avez reçu toutes les sorties lorsque vous obtenez l'événement 'end', ce qui signifie que nous avons besoin d'un autre écouteur et d'un rappel:

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

Donc, votre résultat final est le suivant:

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) }
);
87
James White

Une version simplifiée de la réponse acceptée (troisième point) vient de fonctionner pour moi.

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

J'ai utilisé ceci de manière plus concise:

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

cela fonctionne parfaitement. :)

43
Mimouni

Le plus simple est d’utiliser la librairie ShellJS ...

$ npm install [-g] shelljs

Exemple 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 prend en charge de nombreuses commandes Shell communes mappées en tant que fonctions NodeJS, notamment:

  • chat
  • cd
  • chmod
  • cp
  • dirs
  • écho
  • exec
  • sortie
  • trouver
  • grep
  • ln
  • ls
  • mkdir
  • mv
  • popd
  • pushd
  • pwd
  • rm
  • sed
  • test
  • lequel
42
Tony O'Hagan

J'ai eu un problème similaire et j'ai fini par écrire une extension de noeud pour cela. Vous pouvez consulter le référentiel git. C'est open source et gratuit et tout ce qui est bon!

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

ExecXI est une extension de noeud écrite en C++ pour exécuter des commandes Shell un par un, en envoyant la sortie de la commande à la console dans temps réél. Des manières facultatives chaînées et non chaînées sont présentes; sens que vous pouvez choisir d’arrêter le script après l’échec d’une commande (enchaîné), ou vous pouvez continuer comme si de rien n'était!

Les instructions d'utilisation se trouvent dans le fichier ReadMe . N'hésitez pas à faire des demandes de tirage ou à soumettre des problèmes!

Je pensais que cela valait la peine de le mentionner.

4
Logan

@ TonyO'Hagan est une réponse shelljs complète, mais j'aimerais mettre en évidence la version synchrone de sa réponse:

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

Une version promise de la réponse la plus récompensée:

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

Utiliser:

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

En réalité, vous ne retournez rien de votre fonction 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...'

Fonctionne très bien. :)

0
Chris Eineke

Il y a un conflit de variables dans votre fonction run_cmd:

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

Changez-le simplement en ceci:

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

Pour voir les erreurs, ajoutez toujours ceci:

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

EDITVotre code échoue parce que vous essayez d'exécuter dir qui est pas fourni comme programme autonome distinct. C'est une commande dans le processus cmd. Si vous voulez jouer avec le système de fichiers, utilisez require( 'fs' ) en natif.

Alternativement (ce que je ne recommande pas), vous pouvez créer un fichier batch que vous pouvez ensuite exécuter. Notez que le système d'exploitation par défaut déclenche les fichiers de commandes via cmd.

0
freakish