Exec: exibe stdout “ao vivo”

188

Eu tenho esse script simples:

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

exec('coffee -cw my_file.coffee', function(error, stdout, stderr) {
    console.log(stdout);
});

onde simplesmente executo um comando para compilar um arquivo de script de café. Mas o stdout nunca é exibido no console, porque o comando nunca termina (por causa da opção -w do café). Se eu executar o comando diretamente do console, recebo uma mensagem como esta:

18:05:59 - compiled my_file.coffee

Minha pergunta é: é possível exibir essas mensagens com o node.js exec? Se sim, como? !

obrigado

mravey
fonte
1
Eu vim aqui procurando capturar o stdout do executável do Python. Observe que tudo abaixo funcionará, mas você precisa executar o python com a opção "-u", para tornar a saída sem buffer e, portanto, ter atualizações ao vivo.
Andy

Respostas:

266

Não use exec. Use spawnqual é um EventEmmiterobjeto. Então você pode ouvir stdout/ stderrevents ( spawn.stdout.on('data',callback..)) conforme eles acontecem .

Da documentação do NodeJS:

var spawn = require('child_process').spawn,
    ls    = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', function (data) {
  console.log('stdout: ' + data.toString());
});

ls.stderr.on('data', function (data) {
  console.log('stderr: ' + data.toString());
});

ls.on('exit', function (code) {
  console.log('child process exited with code ' + code.toString());
});

exec armazena em buffer a saída e geralmente a retorna quando o comando termina de executar.

Pooria Azimi
fonte
22
Muito agradável. FYI: O stdout / stderr eventos callback 'dados' argumento é um buffer para chamá-lo com .toString ()
Sergel
4
Para aqueles que não conseguem que o spawn funcione no Windows, dê uma olhada nesta ótima resposta .
precisa saber é o seguinte
17
exec também é um EventEmitter, pelo menos nos últimos.
Nikolay Tsenkov
4
Lembre-se também de que o retorno de chamada não será chamado sempre que o programa emitir uma nova linha. Se você deseja receber "eventos" do processo filho, esse processo deve liberar o buffer (flush(stdout); em C) para disparar eventos no Node.js.
Julian F. Weinert
5
+1 em exec também sendo um EventEmitter .. gastei 2 horas refatorando minha string em uma matriz args (linha de comando ffmpeg muito longa e complicada) .. apenas para descobrir que eu realmente não precisava.
conversas mortas
176

exec também retornará um objeto ChildProcess que é um EventEmitter.

var exec = require('child_process').exec;
var coffeeProcess = exec('coffee -cw my_file.coffee');

coffeeProcess.stdout.on('data', function(data) {
    console.log(data); 
});

OU pipeo stdout do processo filho para o stdout principal.

coffeeProcess.stdout.pipe(process.stdout);

OU herdar stdio usando spawn

spawn('coffee -cw my_file.coffee', { stdio: 'inherit' });
Nathanael Smith
fonte
35
Parece que isso pode ser simplificado usando apenas pipe:coffeeProcess.stdout.pipe(process.stdout);
Eric Freese
3
@ Do EricFreese comentário é o que eu estava procurando, porque eu queria recurso de substituição personagens alavancagem da stdout (aproveitamento transferidor em um script nó)
LoremIpsum
19
Mais simples: spawn(cmd, argv, { stdio: 'inherit' }). Consulte nodejs.org/api/child_process.html#child_process_options_stdio para obter exemplos diferentes.
Morgan Touverey Quilling
3
+1 para @ sugestão de MorganTouvereyQuilling usar spawncom stdio: 'inherit'. Produz uma saída mais precisa do que uma exectubulação stdout/ stderr, por exemplo, ao exibir as informações de progresso de a git clone.
Livven
58

Já existem várias respostas, no entanto, nenhuma delas menciona a melhor (e mais fácil) maneira de fazer isso, que está usando spawne a { stdio: 'inherit' }opção . Parece produzir a saída mais precisa, por exemplo, ao exibir as informações de progresso de umgit clone .

Simplesmente faça o seguinte:

var spawn = require('child_process').spawn;

spawn('coffee', ['-cw', 'my_file.coffee'], { stdio: 'inherit' });

Os nossos agradecimentos a @MorganTouvereyQuilling por apontar isso neste comentário .

Livven
fonte
1
Descobri que quando o subprocesso usa saída formatada como texto colorido, stdio: "inherit"preserva essa formatação enquanto child.stdout.pipe(process.stdout)não.
Rikki Gibson 21/09
Isso preserva perfeitamente a saída, mesmo em processos com saída complexa, como as barras de progresso nas instalações do npm. Impressionante!
Dave Koo
1
por que essa não é a resposta aceita? foi o único que funcionou para mim e é apenas 2 f * linhas !!!
Lincoln
Esta dica foi útil ao executar alguns aplicativos de linha de comando do Symfony que usam barras de progresso. Felicidades.
Halfstop 16/07/19
Essa deve ser a resposta aceita - única coisa que preserva a representação perfeita da saída e é a mais simples? sim por favor
evnp 31/03
21

Gostaria apenas de acrescentar que um pequeno problema com a saída das seqüências de buffer de um processo gerado console.log()é que ela adiciona novas linhas, que podem espalhar a saída do processo gerado por linhas adicionais. Se você imprimir stdoutou stderrcom em process.stdout.write()vez deconsole.log() , obterá a saída do console do processo gerado 'como está'.

Eu vi essa solução aqui: Node.js: imprimindo no console sem uma nova linha à direita?

Espero que ajude alguém a usar a solução acima (que é ótima para saída ao vivo, mesmo que seja da documentação).

Kevin Teljeur
fonte
1
Para uso de saída ainda mais preciso spawn(command, args, { stdio: 'inherit' }), conforme sugerido por @MorganTouvereyQuilling aqui stackoverflow.com/questions/10232192/…
Livven
21

Inspirado na resposta de Nathanael Smith e no comentário de Eric Freese, pode ser tão simples quanto:

var exec = require('child_process').exec;
exec('coffee -cw my_file.coffee').stdout.pipe(process.stdout);
Tyler Long
fonte
Isso parece funcionar bem para comandos simples como, lsmas falha em comandos mais complexos, como npm install. Até tentei canalizar stdout e stderr para seus respectivos objetos de processo.
Linuxdan 19/08/19
@linuxdan pode ser porque o npm está escrevendo no stderr (vi alguns escreverem a barra de progresso lá). você também pode canalizar stderr ou estender a solução Tongfa para ouvir no stderr.
Sergiu
@linuxdan Pelo que vi da maneira mais confiável spawn(command, args, { stdio: 'inherit' }), como sugerido aqui stackoverflow.com/questions/10232192/…
Livven
Melhor resposta, obrigado por isso. Trabalhou como um encanto
Abhishek Sharma
12

Eu achei útil adicionar um script exec personalizado aos meus utilitários que fazem isso.

utilities.js

const { exec } = require('child_process')

module.exports.exec = (command) => {
  const process = exec(command)

  process.stdout.on('data', (data) => {
    console.log('stdout: ' + data.toString())
  })

  process.stderr.on('data', (data) => {
    console.log('stderr: ' + data.toString())
  })

  process.on('exit', (code) => {
    console.log('child process exited with code ' + code.toString())
  })
}

app.js

const { exec } = require('./utilities.js')

exec('coffee -cw my_file.coffee')
IanLancaster
fonte
5

Depois de revisar todas as outras respostas, acabei com isso:

function oldSchoolMakeBuild(cb) {
    var makeProcess = exec('make -C ./oldSchoolMakeBuild',
         function (error, stdout, stderr) {
             stderr && console.error(stderr);
             cb(error);
        });
    makeProcess.stdout.on('data', function(data) {
        process.stdout.write('oldSchoolMakeBuild: '+ data);
    });
}

Às vezes, datahaverá várias linhas, portanto o oldSchoolMakeBuildcabeçalho aparecerá uma vez para várias linhas. Mas isso não me incomodou o suficiente para mudar isso.

Tongfa
fonte
3

child_process.spawn retorna um objeto com os fluxos stdout e stderr. Você pode tocar no fluxo stdout para ler os dados que o processo filho envia de volta ao Nó. stdout sendo um fluxo possui os "dados", "fim" e outros eventos que os fluxos têm. o spawn é melhor usado quando você deseja que o processo filho retorne uma grande quantidade de dados ao Nó - processamento de imagem, leitura de dados binários etc.

para que você possa resolver seu problema usando child_process.spawn, conforme usado abaixo.

var spawn = require('child_process').spawn,
ls = spawn('coffee -cw my_file.coffee');

ls.stdout.on('data', function (data) {
  console.log('stdout: ' + data.toString());
});

ls.stderr.on('data', function (data) {
  console.log('stderr: ' + data.toString());
});

ls.on('exit', function (code) {
  console.log('code ' + code.toString());
});
Adeojo Emmanuel IMM
fonte
1

Aqui está uma função auxiliar assíncrona escrita em texto datilografado que parece fazer o truque para mim. Eu acho que isso não vai funcionar para processos de longa duração, mas ainda pode ser útil para alguém?

import * as child_process from "child_process";

private async spawn(command: string, args: string[]): Promise<{code: number | null, result: string}> {
    return new Promise((resolve, reject) => {
        const spawn = child_process.spawn(command, args)
        let result: string
        spawn.stdout.on('data', (data: any) => {
            if (result) {
                reject(Error('Helper function does not work for long lived proccess'))
            }
            result = data.toString()
        })
        spawn.stderr.on('data', (error: any) => {
            reject(Error(error.toString()))
        })
        spawn.on('exit', code => {
            resolve({code, result})
        })
    })
}
Swaner
fonte