Ejecutivo: mostrar stdout "en vivo"

188

Tengo este script simple:

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

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

donde simplemente ejecuto un comando para compilar un archivo de script de café. Pero stdout nunca se muestra en la consola, porque el comando nunca termina (debido a la opción -w de coffee). Si ejecuto el comando directamente desde la consola, recibo un mensaje como este:

18:05:59 - compiled my_file.coffee

Mi pregunta es: ¿es posible mostrar estos mensajes con el nodo.js exec? Si es así, ¿cómo? !

Gracias

mravey
fuente
1
Vine aquí buscando capturar stdout del ejecutable de Python. Tenga en cuenta que todo lo siguiente funcionará, pero debe ejecutar Python con la opción "-u", para que el búfer no esté protegido y, por lo tanto, tenga actualizaciones en vivo.
Andy

Respuestas:

266

No utilice exec. Uso spawnque es un EventEmmiterobjeto. Luego puede escuchar stdout/ stderreventos ( spawn.stdout.on('data',callback..)) a medida que suceden .

De la documentación de 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 almacena el resultado y generalmente lo devuelve cuando el comando ha terminado de ejecutarse.

Pooria Azimi
fuente
22
Muy agradable. FYI: El argumento de devolución de llamada de eventos stdout / stderr 'data' es un búfer, así que llámelo con .toString ()
SergeL
44
Para aquellos de ustedes que no pueden engendrar para trabajar en Windows, echen un vistazo a esta excelente respuesta .
tomekwi
17
exec también es un EventEmitter al menos en lo último.
Nikolay Tsenkov
44
También tenga en cuenta que no se llamará a la devolución de llamada, siempre que el programa genere una nueva línea. Si desea recibir "eventos" del proceso secundario, este proceso debe vaciar el búfer ( flush(stdout);en C) para activar eventos en Node.js.
Julian F. Weinert
55
+1 en exec también es un EventEmitter ... pasé 2 horas refactorizando mi cadena en una matriz de args (línea de comando ffmpeg muy larga y complicada) ... solo para descubrir que realmente no era necesario.
deadconversations
176

exec también devolverá un objeto ChildProcess que es un EventEmitter.

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

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

O pipela salida estándar del proceso secundario a la salida estándar principal.

coffeeProcess.stdout.pipe(process.stdout);

O heredar stdio usando spawn

spawn('coffee -cw my_file.coffee', { stdio: 'inherit' });
Nathanael Smith
fuente
35
Parece que esto se puede simplificar simplemente usando pipe:coffeeProcess.stdout.pipe(process.stdout);
Eric Freese
3
El comentario de @ EricFreese es lo que estaba buscando, porque quería aprovechar la función de reemplazo de caracteres de stdout (aprovechando el transportador en un script de nodo)
LoremIpsum
19
Más simple: spawn(cmd, argv, { stdio: 'inherit' }). Consulte nodejs.org/api/child_process.html#child_process_options_stdio para ver diferentes ejemplos.
Morgan Touverey Quilling
3
1 para @ sugerencia de MorganTouvereyQuilling a utilizar spawncon stdio: 'inherit'. Produce resultados más precisos que execy tuberías stdout/ stderr, por ejemplo, cuando se muestra la información de progreso de a git clone.
Livven
58

Ya hay varias respuestas, sin embargo, ninguna de ellas menciona la mejor (y más fácil) forma de hacerlo, que es usar spawny la { stdio: 'inherit' }opción . Parece producir la salida más precisa, por ejemplo, cuando se muestra la información de progreso de a git clone.

Simplemente haz esto:

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

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

Gracias a @MorganTouvereyQuilling por señalar esto en este comentario .

Livven
fuente
1
Descubrí que cuando el subproceso usa salida formateada como texto en color, stdio: "inherit"conserva ese formato mientras child.stdout.pipe(process.stdout)que no lo hace.
Rikki Gibson
Esto preserva perfectamente la salida incluso en procesos con salida compleja como las barras de progreso en las instalaciones npm. ¡Increíble!
Dave Koo
1
¿Por qué esta no es la respuesta aceptada? ¡fue el único que funcionó para mí y son solo 2 líneas f *!
Lincoln
Este consejo fue útil al ejecutar algunas aplicaciones de línea de comandos de Symfony que usan barras de progreso. Salud.
Halfstop
Esta debería ser la respuesta aceptada, ¿la única cosa que conserva una representación de salida perfecta y es la más simple? sí por favor
evnp
21

Solo me gustaría agregar que un pequeño problema con la salida de las cadenas de búfer de un proceso generado console.log()es que agrega nuevas líneas, que pueden extender su salida del proceso generado a través de líneas adicionales. Si sale stdouto stderrcon en process.stdout.write()lugar de console.log(), obtendrá la salida de la consola del proceso generado 'tal cual'.

Vi esa solución aquí: Node.js: ¿imprimiendo en la consola sin una nueva línea final?

Espero que ayude a alguien a usar la solución anterior (que es excelente para la salida en vivo, incluso si es de la documentación).

Kevin Teljeur
fuente
1
Para un uso de salida aún más preciso spawn(command, args, { stdio: 'inherit' }), como lo sugiere @MorganTouvereyQuilling aquí stackoverflow.com/questions/10232192/…
Livven
21

Inspirado por la respuesta de Nathanael Smith y el comentario de Eric Freese, podría ser tan simple como:

var exec = require('child_process').exec;
exec('coffee -cw my_file.coffee').stdout.pipe(process.stdout);
Tyler Long
fuente
Esto parece funcionar bien para comandos simples como lspero falla para comandos más complejos como npm install. Incluso intenté conectar tanto stdout como stderr a sus respectivos objetos de proceso.
linuxdan
@linuxdan puede ser porque npm está escribiendo en stderr (vi que algunos escribían la barra de progreso allí). también puede canalizar stderr, o extender la solución Tongfa para escuchar en stderr.
Sergiu
@linuxdan Por lo que he visto, la forma más confiable es spawn(command, args, { stdio: 'inherit' }), como se sugiere aquí stackoverflow.com/questions/10232192/…
Livven
La mejor respuesta, gracias por esto. Trabajó como un encanto
Abhishek Sharma
12

He encontrado útil agregar un script de ejecución personalizado a mis utilidades que hacen esto.

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
fuente
5

Después de revisar todas las otras respuestas, terminé con esto:

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

A veces datahabrá varias líneas, por lo que el oldSchoolMakeBuildencabezado aparecerá una vez para varias líneas. Pero esto no me molestó lo suficiente como para cambiarlo.

Tongfa
fuente
3

child_process.spawn devuelve un objeto con secuencias stdout y stderr. Puede tocar la secuencia stdout para leer los datos que el proceso secundario envía de vuelta al Nodo. stdout siendo una secuencia tiene los "datos", "fin" y otros eventos que tienen las secuencias. Spawn se utiliza mejor cuando desea que el proceso secundario devuelva una gran cantidad de datos al nodo: procesamiento de imágenes, lectura de datos binarios, etc.

para que pueda resolver su problema usando child_process.spawn como se usa a continuación.

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
fuente
1

Aquí hay una función auxiliar asíncrona escrita en mecanografiado que parece hacer el truco para mí. Supongo que esto no funcionará para procesos de larga duración, pero ¿podría ser útil para alguien?

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
fuente