execução do comando shell node.js


113

Ainda estou tentando entender os pontos mais delicados de como posso executar um comando linux ou windows shell e capturar a saída em node.js; no final das contas, eu quero fazer algo assim ...

//pseudocode
output = run_command(cmd, args)

A parte importante é que outputdeve estar disponível para uma variável com escopo global (ou objeto). Tentei a seguinte função, mas por algum motivo, fui undefinedimpresso no 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" <------

Estou tendo problemas para entender onde o código quebra acima ... um protótipo muito simples desse modelo funciona ...

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

Alguém pode me ajudar a entender por que try_this()funciona e run_cmd()não funciona? FWIW, preciso usar child_process.spawn, porque child_process.exectem um limite de buffer de 200 KB .

Resolução Final

Estou aceitando a resposta de Tiago White, mas este é o código exato que funcionou para mim ...

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
Na resolução final você deve definir me.stdout = "";em cmd_exec()para evitar concatenação undefinedpara o início do resultado.
aorcsik

Ei, esse código de resolução final é completamente horrível, e se demorar mais de 0,25 segundo para executar seu netstat?
Steven Lu

Ummmm ... talvez use uma das respostas para as quais eu atribuí um bônus ?????
Mike Pennington

Respostas:


89

Existem três problemas aqui que precisam ser corrigidos:

A primeira é que você está esperando um comportamento síncrono ao usar o stdout de forma assíncrona. Todas as chamadas em sua run_cmdfunção são assíncronas, portanto, ele gerará o processo filho e retornará imediatamente, independentemente de alguns, todos ou nenhum dos dados terem sido lidos no stdout. Como tal, quando você executa

console.log(foo.stdout);

você obtém o que quer que esteja armazenado em foo.stdout no momento, e não há garantia do que será, porque seu processo filho ainda pode estar em execução.

O segundo é que stdout é um fluxo legível , portanto 1) o evento de dados pode ser chamado várias vezes e 2) o retorno de chamada recebe um buffer, não uma string. Fácil de remediar; Apenas mude

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

para dentro

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

para que possamos converter nosso buffer em uma string e anexar essa string à nossa variável stdout.

Em terceiro lugar, você só pode saber que recebeu todas as saídas ao obter o evento 'final', o que significa que precisamos de outro ouvinte e retorno de chamada:

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

Então, seu resultado final é este:

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

"não há garantia do que será porque seu processo filho ainda pode estar em execução" close ... mas há uma garantia de que não será definido naquele momento e só será definido quando o retorno de chamada for finalmente chamado, como você indicou em outro lugar

4
Esta é uma excelente resposta com ótimas explicações sobre alguns conceitos muito importantes de JS. Agradável!
L0j1k

1
Você vai querer fazer this.stdout = "";dentro da run()função, caso contrário, seu console.log(foo.sdtout);será prefixado com undefined.
f1lt3r

78

Uma versão simplificada da resposta aceita (terceiro ponto) funcionou para mim.

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 quanto ao valor de retorno, quando o processo retorna um valor diferente de zero, como posso obtê-lo?
Vitim.us

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

52

Usei isso de forma mais concisa:

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

funciona perfeitamente. :)


1
Isso funciona bem e não requer nenhum módulo de nó adicional. Eu gosto disso!
bearvarine,

9
sys.puts()foi descontinuado em 2011 (com Node.js v0.2.3). Você deve usar em seu console.log()lugar.
tfmontague 05 de

1
isso realmente funciona ... então eu me pergunto, por que não é esta a resposta? é tão simples
ekkis

Uau!! esta resposta é perfeita e elegante. obrigado.
Em Ji Madhu

43

A maneira mais simples é apenas usar a biblioteca ShellJS ...

$ npm install [-g] shelljs

Exemplo 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 suporta muitos comandos shell comuns mapeados como funções NodeJS, incluindo:

  • gato
  • CD
  • chmod
  • cp
  • dirs
  • eco
  • exec
  • Saída
  • encontrar
  • grep
  • em
  • ls
  • mkdir
  • mv
  • popd
  • empurrar
  • pwd
  • rm
  • sed
  • teste
  • qual

como adicionar um parâmetro ao script de shell chamado por shell.exec ("foo.sh")?
pseudozach

1
Você pode simplesmente acrescentar os seus argumentos no topo da cadeia: shell.exec("foo.sh arg1 arg2 ... "). Seu foo.shscript pode fazer referência a eles usando $1, $2... etc.
Tony O'Hagan

Se o comando que você está tentando executar precisar da entrada do usuário, NÃO use ShellJS exec (). Esta função não é interativa por natureza, uma vez que apenas aceita o comando e imprime a saída. Não é possível aceitar entradas no meio. Em vez disso, use child_process integrado. Por exemplo, https://stackoverflow.com/a/31104898/9749509
MPatel1

4

Eu tive um problema semelhante e acabei escrevendo uma extensão de nó para isso. Você pode verificar o repositório git. É open source e gratuito e todas essas coisas boas!

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

ExecXI é uma extensão de nó escrita em C ++ para executar comandos shell um por um, enviando a saída do comando para o console em tempo real. Formas opcionais encadeadas e desencadeadas estão presentes; o que significa que você pode optar por parar o script após a falha de um comando (encadeado) ou pode continuar como se nada tivesse acontecido!

As instruções de uso estão no arquivo LeiaMe . Sinta-se à vontade para fazer solicitações de pull ou enviar problemas!

Achei que valia a pena mencionar isso.


4

@ TonyO'Hagan é uma shelljsresposta abrangente , mas, gostaria de destacar a versão síncrona de sua resposta:

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

1

One-liner síncrono:

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


0

Há um conflito de variável em sua run_cmdfunção:

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

Simplesmente mude para este:

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

Para ver os erros, sempre adicione:

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

EDITAR Seu código falha porque você está tentando executar o dirque não é fornecido como um programa autônomo separado. É um comando em cmdandamento. Se você quiser brincar com o sistema de arquivos, use o nativo require( 'fs' ).

Como alternativa (o que eu não recomendo), você pode criar um arquivo em lote que pode ser executado. Observe que o sistema operacional, por padrão, dispara arquivos em lote via cmd.


obrigado por sua ajuda ... entretanto, mesmo quando eu corro C:\Windows\System32\netstat.exe, isso ainda não produz resultados ... Minha sintaxe exata era foo = new run_cmd('netstat.exe', ['-an'], function (me, data){me.stdout=data;});... Eu também tentei o caminho completo sem sucesso até agora
Mike Pennington

0

Na verdade, você não está retornando nada da função 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...'

Funciona muito bem. :)


Não acho que returnseja necessário, desde que você defina corretamente os atributos do objeto
Mike Pennington

0

Uma versão prometida da resposta mais premiada:

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

Usar:

 runCmd('ls').then(ret => console.log(ret))
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.