117 votes

exécution de commandes shell node.js

J'essaie toujours de saisir les points les plus fins de la façon dont je peux exécuter une commande shell linux ou Windows et capturer la sortie dans node.js ; finalement, je veux faire quelque chose comme ceci...

//pseudocode
output = run_command(cmd, args)

L'élément important 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, j'obtiens undefined imprimé à 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" <------

J'ai du mal à comprendre où le code est cassé ci-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() travaux, et run_cmd() ne le fait pas ? Pour info, j'ai besoin d'utiliser child_process.spawn parce que child_process.exec a une limite de mémoire tampon de 200KB.

Résolution finale

J'accepte la réponse de James White, mais voici 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);

2 votes

Dans la résolution finale, vous devez définir me.stdout = ""; sur cmd_exec() pour empêcher la concaténation undefined au début du résultat.

0 votes

Hé, ce code de résolution finale est complètement affreux, et si cela prend plus de 0,25 seconde pour exécuter votre netstat ?

0 votes

Ummmm... peut-être utiliser une des réponses auxquelles j'ai attribué un bonus ?????.

89voto

James White Points 266

Il y a trois problèmes ici qui doivent être résolus :

Premier est que vous attendez un comportement synchrone alors que vous utilisez stdout de manière asynchrone. Tous les appels dans votre run_cmd sont asynchrones, c'est-à-dire qu'elles vont lancer le processus enfant et revenir immédiatement, que certaines, toutes ou aucune des données aient été lues à partir de stdout. Ainsi, lorsque vous exécutez

console.log(foo.stdout);

vous obtenez ce qui se trouve être stocké dans foo.stdout à ce moment, et il n'y a aucune garantie de ce que ce sera parce que votre processus enfant peut être encore en cours d'exécution.

Deuxièmement est que stdout est un flux lisible Ainsi, 1) l'événement de données peut être appelé plusieurs fois, et 2) la fonction de rappel reçoit un tampon et non une chaîne. Il est facile d'y remédier ; il suffit de modifier

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

en

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

afin de convertir notre tampon en une chaîne et d'ajouter cette chaîne à notre variable stdout.

Troisièmement est que vous ne pouvez savoir que vous avez reçu toute la sortie que lorsque vous obtenez l'événement "end", ce qui signifie que nous avons besoin d'un autre écouteur et d'un autre callback :

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

0 votes

"il n'y a aucune garantie de ce que ce sera parce que votre processus enfant pourrait encore être en cours d'exécution" fermer ... mais il ya une garantie qu'il sera no ne sera pas défini à ce moment-là, mais seulement lorsque la fonction de rappel sera finalement appelée, comme vous l'avez indiqué ailleurs.

4 votes

Il s'agit d'une excellente réponse avec de bonnes explications de certains concepts JS très importants. Très bien !

1 votes

Vous voudrez faire this.stdout = ""; à l'intérieur de la run() sinon votre console.log(foo.sdtout); seront préfixés par undefined .

79voto

cibercitizen1 Points 3093

Une version simplifiée de la réponse acceptée (troisième point), a fonctionné 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) });
} // ()

Utilisation :

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

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

1 votes

Qu'en est-il de la valeur de retour, lorsque le processus renvoie une valeur non nulle, comment puis-je l'obtenir ?

2 votes

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

52voto

Ilyas Mimouni Points 279

Je l'ai utilisé 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 :)

1 votes

Cela fonctionne bien, et ne nécessite pas de modules de nœuds supplémentaires. Je l'apprécie !

10 votes

sys.puts() a été déprécié en 2011 (avec Node.js v0.2.3). Vous devez utiliser console.log() à la place.

1 votes

Qui fonctionne réellement... alors je me demande, pourquoi ce n'est pas la réponse ? C'est tellement simple...

45voto

Tony O'Hagan Points 1330

Le moyen le plus simple est d'utiliser la librairie ShellJS ...

$ npm install [-g] shelljs

EXEC Exemple :

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 courantes mappées comme des fonctions NodeJS, notamment :

  • chat
  • cd
  • chmod
  • cp
  • répertoires
  • echo
  • exec
  • quitter
  • trouver
  • grep
  • ln
  • ls
  • mkdir
  • mv
  • popd
  • pushd
  • pwd
  • rm
  • sed
  • test
  • dont

0 votes

Comment ajouter un paramètre au shell script appelé par shell.exec("foo.sh") ?

1 votes

Vous pouvez simplement ajouter vos arguments à la chaîne : shell.exec("foo.sh arg1 arg2 ... ") . Votre foo.sh script peut les référencer en utilisant $1 , $2 ... etc.

0 votes

Si la commande que vous essayez d'exécuter nécessite une entrée de l'utilisateur, n'utilisez pas ShellJS exec(). Cette fonction n'est pas interactive par nature, car elle ne fait que prendre la commande et imprimer la sortie, elle ne peut pas accepter d'entrées entre les deux. Utilisez le child_process intégré à la place. Par exemple https://stackoverflow.com/a/31104898/9749509

4voto

Logan Points 2012

J'ai eu un problème similaire et j'ai fini par écrire une extension node pour cela. Vous pouvez consulter le dépôt git. C'est une source ouverte et gratuite et toutes ces bonnes choses !

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

ExecXI est une extension de nœud écrite en C++ pour exécuter des commandes shell. une par une, en affichant la sortie de la commande sur la console en temps réel. temps réel. Des méthodes optionnelles chaînées et non chaînées sont présentes ; ce qui signifie que que vous pouvez choisir d'arrêter le script après l'échec d'une commande (chaîné), ou vous pouvez continuer comme si rien ne s'était passé !

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

J'ai pensé que cela valait la peine de le mentionner.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X