Execute e obtenha a saída de um comando shell em node.js

113

Em um node.js, gostaria de encontrar uma maneira de obter a saída de um comando de terminal Unix. Há alguma maneira de fazer isso?

function getCommandOutput(commandString){
    // now how can I implement this function?
    // getCommandOutput("ls") should print the terminal output of the shell command "ls"
}
Anderson Green
fonte
É uma duplicata ou descreve algo completamente diferente? stackoverflow.com/questions/7183307/…
Anderson Green,
Isso pode interessar a você.
Benekastah,
Use npmjs.com/package/cross-spawn
Andrew Koster,

Respostas:

142

É assim que faço em um projeto em que estou trabalhando agora.

var exec = require('child_process').exec;
function execute(command, callback){
    exec(command, function(error, stdout, stderr){ callback(stdout); });
};

Exemplo: Recuperando o usuário git

module.exports.getGitUser = function(callback){
    execute("git config --global user.name", function(name){
        execute("git config --global user.email", function(email){
            callback({ name: name.replace("\n", ""), email: email.replace("\n", "") });
        });
    });
};
Renato Gama
fonte
3
É possível fazer com que esta função retorne a saída do comando? (Isso é o que eu estava tentando fazer.)
Anderson Green,
1
isso é o que esse código faz. veja o exemplo da edição que acabei de fazer
Renato Gama
2
@AndersonGreen Você não gostaria que a função retornasse normalmente com o teclado "return", porque ela está executando o comando shell de forma assíncrona. Como resultado, é melhor passar um retorno de chamada com código que deve ser executado quando o comando shell for concluído.
Nick McCurdy
1
Ouch, sua primeira amostra ignora a possibilidade de um erro ao chamar esse retorno de chamada. Eu me pergunto o que acontecerá stdoutse houver um erro. Esperançosamente determinístico e documentado.
doug65536,
31

Você está procurando child_process

var exec = require('child_process').exec;
var child;

child = exec(command,
   function (error, stdout, stderr) {
      console.log('stdout: ' + stdout);
      console.log('stderr: ' + stderr);
      if (error !== null) {
          console.log('exec error: ' + error);
      }
   });

Como apontado por Renato, existem alguns pacotes exec síncronos por aí agora também, veja sync-exec que pode ser mais o que você está procurando. Lembre-se, porém, de que o node.js foi projetado para ser um servidor de rede de alto desempenho com thread único, então, se é para isso que você está procurando, fique longe de coisas do tipo sync-exec, a menos que você o use apenas durante a inicialização ou alguma coisa.

hexista
fonte
1
Nesse caso, como posso obter a saída do comando? É "stdout" que contém a saída da linha de comando?
Anderson Green,
Além disso, é possível fazer algo semelhante sem usar um retorno de chamada?
Anderson Green,
Correto, stdout contém a saída do programa. E não, não é possível fazer isso sem callbacks. Tudo em node.js é orientado para não bloquear, o que significa que toda vez que você fizer IO, você usará callbacks.
hexista de
Note que se você está procurando usar javascript para fazer coisas com script onde você realmente quer esperar pela saída e esse tipo de coisa, você pode olhar para o shell v8, d8
hexist
@hexist existem alguns Syncmétodos nativamente disponíveis, embora IMHO deva ser evitado
Renato Gama
29

Se você está usando o node posterior a 7.6 e não gosta do estilo de retorno de chamada, você também pode usar a promisifyfunção do node-util com async / awaitpara obter comandos shell que sejam lidos de forma limpa. Aqui está um exemplo de resposta aceita, usando esta técnica:

const { promisify } = require('util');
const exec = promisify(require('child_process').exec)

module.exports.getGitUser = async function getGitUser () {
  const name = await exec('git config --global user.name')
  const email = await exec('git config --global user.email')
  return { name, email }
};

Isso também tem o benefício adicional de retornar uma promessa rejeitada em comandos com falha, que pode ser tratada try / catchdentro do código assíncrono.

Ansikt
fonte
Você já tentou isso? Estou recebendo { stdout: string, stderr: string }como resultado paraawait exec(...)
fwoelffel de
1
Sim, eu deveria ter esclarecido que isso fornece a saída completa do shell, incluindo stdout e stderr. Se você quer apenas a saída, você pode mudar a última linha para: return { name: name.stdout.trim(), email: email.stdout.trim() }.
Ansikt
16

Graças à resposta do Renato, criei um exemplo realmente básico:

const exec = require('child_process').exec

exec('git config --global user.name', (err, stdout, stderr) => console.log(stdout))

Ele apenas imprimirá seu nome de usuário git global :)

Damjan Pavlica
fonte
11

Requisitos

Isso exigirá Node.js 7 ou posterior com suporte para Promises e Async / Await.

Solução

Crie uma função de wrapper que aproveite as promessas de controlar o comportamento do child_process.execcomando.

Explicação

Usando promessas e uma função assíncrona, você pode imitar o comportamento de um shell retornando a saída, sem cair em um inferno de callback e com uma API bem organizada. Usando a awaitpalavra - chave, você pode criar um script de fácil leitura e, ao mesmo tempo, realizar o trabalho child_process.exec.

Amostra de código

const childProcess = require("child_process");

/**
 * @param {string} command A shell command to execute
 * @return {Promise<string>} A promise that resolve to the output of the shell command, or an error
 * @example const output = await execute("ls -alh");
 */
function execute(command) {
  /**
   * @param {Function} resolve A function that resolves the promise
   * @param {Function} reject A function that fails the promise
   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
   */
  return new Promise(function(resolve, reject) {
    /**
     * @param {Error} error An error triggered during the execution of the childProcess.exec command
     * @param {string|Buffer} standardOutput The result of the shell command execution
     * @param {string|Buffer} standardError The error resulting of the shell command execution
     * @see https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
     */
    childProcess.exec(command, function(error, standardOutput, standardError) {
      if (error) {
        reject();

        return;
      }

      if (standardError) {
        reject(standardError);

        return;
      }

      resolve(standardOutput);
    });
  });
}

Uso

async function main() {
  try {
    const passwdContent = await execute("cat /etc/passwd");

    console.log(passwdContent);
  } catch (error) {
    console.error(error.toString());
  }

  try {
    const shadowContent = await execute("cat /etc/shadow");

    console.log(shadowContent);
  } catch (error) {
    console.error(error.toString());
  }
}

main();

Saída de amostra

root:x:0:0::/root:/bin/bash
[output trimmed, bottom line it succeeded]

Error: Command failed: cat /etc/shadow
cat: /etc/shadow: Permission denied

Experimente online.

Repl.it .

Fontes externas

Promessas .

child_process.exec.

Mesa de apoio Node.js .

Amin NAIRI
fonte