Node.js genera un proceso secundario y obtiene la salida del terminal en vivo

113

Tengo un script que emite 'hola', duerme un segundo, emite 'hola', duerme 1 segundo, y así sucesivamente. Ahora pensé que podría abordar este problema con este modelo.

var spawn = require('child_process').spawn,
temp    = spawn('PATH TO SCRIPT WITH THE ABOVE BEHAVIOUR');

temp.stdout.pipe(process.stdout);

Ahora el problema es que la tarea debe completarse para que se muestre el resultado. Según tengo entendido, esto se debe al hecho de que el proceso recién generado toma el control de ejecución. Obviamente, node.js no admite subprocesos, ¿hay alguna solución? Mi idea era posiblemente ejecutar dos instancias, la primera con el propósito específico de crear la tarea y hacer que canalice la salida al proceso de la segunda instancia, considerando que esto se puede lograr.

foklepoint
fuente
1
Si el proceso hijo se escribe pythona continuación, no se olvide de pasar -ubandera para que no búfer de salida de la consola, de lo contrario se verá como la escritura no es en vivo stackoverflow.com/a/49947671/906265
Aivaras
Utilice npmjs.com/package/cross-spawn en lugar de cualquier otra cosa. Es simplemente mejor.
Andrew Koster

Respuestas:

92

Todavía me estoy mojando los pies con Node.js, pero tengo algunas ideas. primero, creo que necesitas usar en execFilelugar de spawn; execFilees para cuando tiene la ruta a un script, mientras que spawnes para ejecutar un comando conocido que Node.js puede resolver contra la ruta de su sistema.

1. Proporcione una devolución de llamada para procesar la salida almacenada en búfer:

var child = require('child_process').execFile('path/to/script', [ 
    'arg1', 'arg2', 'arg3', 
], function(err, stdout, stderr) { 
    // Node.js will invoke this callback when process terminates.
    console.log(stdout); 
});  

2. Agregue un oyente al flujo stdout del proceso secundario ( 9thport.net )

var child = require('child_process').execFile('path/to/script', [ 
    'arg1', 'arg2', 'arg3' ]); 
// use event hooks to provide a callback to execute when data are available: 
child.stdout.on('data', function(data) {
    console.log(data.toString()); 
});

Además, parece haber opciones mediante las cuales puede separar el proceso generado de la terminal de control de Node, lo que le permitiría ejecutarse de forma asincrónica. Aún no lo he probado, pero hay ejemplos en los documentos de la API que son algo como esto:

child = require('child_process').execFile('path/to/script', [ 
    'arg1', 'arg2', 'arg3', 
], { 
    // detachment and ignored stdin are the key here: 
    detached: true, 
    stdio: [ 'ignore', 1, 2 ]
}); 
// and unref() somehow disentangles the child's event loop from the parent's: 
child.unref(); 
child.stdout.on('data', function(data) {
    console.log(data.toString()); 
});
RubyTuesdayDONO
fuente
8
Puntos de bonificación si puede explicar cómo hacer esto con exec () ya que necesito ejecutar un cmd de shell.
DynamicDan
2
Puede utilizar child.spawn()con la shellopción establecida en true. nodejs.org/api/…
CedX
5
También puede canalizar child.stdout directamente a process.stdout conchild.stdout.pipe(process.stdout);
darkadept
@DynamicDan javascript let childProcess = exec ( './script-to-run --arg1 arg1value', ( error, stdout, stderror ) => { console.log( '[CALLBACK]: ' + error ); // or stdout or stderror } ); // Same as with spawn: childProcess.stdout.on ( 'data', ( data ) => { console.log( '[LIVE]: ' + data ); // Here's your live data! } );
Rik
130

¡Es mucho más fácil ahora (6 años después)!

Spawn devuelve un childObject , con el que luego puedes escuchar eventos . Los eventos son:

  • Clase: ChildProcess
    • Evento: 'error'
    • Evento: 'salir'
    • Evento: 'cerrar'
    • Evento: 'desconectar'
    • Evento: 'mensaje'

También hay un montón de objetos de childObject , que son:

  • Clase: ChildProcess
    • child.stdin
    • child.stdout
    • child.stderr
    • child.stdio
    • child.pid
    • niño.conectado
    • child.kill ([señal])
    • child.send (mensaje [, sendHandle] [, devolución de llamada])
    • child.disconnect ()

Vea más información aquí sobre childObject: https://nodejs.org/api/child_process.html

Asincrónico

Si desea ejecutar su proceso en segundo plano mientras el nodo aún puede continuar ejecutándose, use el método asincrónico. Aún puede optar por realizar acciones después de que se complete su proceso y cuando el proceso tenga alguna salida (por ejemplo, si desea enviar la salida de un script al cliente).

child_process.spawn (...); (Nodo v0.1.90)

var spawn = require('child_process').spawn;
var child = spawn('node ./commands/server.js');

// You can also use a variable to save the output 
// for when the script closes later
var scriptOutput = "";

child.stdout.setEncoding('utf8');
child.stdout.on('data', function(data) {
    //Here is where the output goes

    console.log('stdout: ' + data);

    data=data.toString();
    scriptOutput+=data;
});

child.stderr.setEncoding('utf8');
child.stderr.on('data', function(data) {
    //Here is where the error output goes

    console.log('stderr: ' + data);

    data=data.toString();
    scriptOutput+=data;
});

child.on('close', function(code) {
    //Here you can get the exit code of the script

    console.log('closing code: ' + code);

    console.log('Full output of script: ',scriptOutput);
});

Así es como usaría un método callback + asincrónico :

var child_process = require('child_process');

console.log("Node Version: ", process.version);

run_script("ls", ["-l", "/home"], function(output, exit_code) {
    console.log("Process Finished.");
    console.log('closing code: ' + exit_code);
    console.log('Full output of script: ',output);
});

console.log ("Continuing to do node things while the process runs at the same time...");

// This function will output the lines from the script 
// AS is runs, AND will return the full combined output
// as well as exit code when it's done (using the callback).
function run_script(command, args, callback) {
    console.log("Starting Process.");
    var child = child_process.spawn(command, args);

    var scriptOutput = "";

    child.stdout.setEncoding('utf8');
    child.stdout.on('data', function(data) {
        console.log('stdout: ' + data);

        data=data.toString();
        scriptOutput+=data;
    });

    child.stderr.setEncoding('utf8');
    child.stderr.on('data', function(data) {
        console.log('stderr: ' + data);

        data=data.toString();
        scriptOutput+=data;
    });

    child.on('close', function(code) {
        callback(scriptOutput,code);
    });
}

Usando el método anterior, puede enviar cada línea de salida del script al cliente (por ejemplo, usando Socket.io para enviar cada línea cuando recibe eventos en stdoutostderr ).

Sincrónico

Si desea que el nodo detenga lo que está haciendo y espere hasta que se complete el script , puede usar la versión sincrónica:

child_process.spawnSync (...);(Nodo v0.11.12 +)

Problemas con este método:

  • Si la secuencia de comandos tarda un poco en completarse, su servidor se bloqueará durante ese tiempo.
  • La salida estándar solo se devolverá una vez que el script haya terminado de ejecutarse . Debido a que es síncrono, no puede continuar hasta que finalice la línea actual. Por lo tanto, no puede capturar el evento 'stdout' hasta que la línea de generación haya terminado.

Cómo usarlo:

var child_process = require('child_process');

var child = child_process.spawnSync("ls", ["-l", "/home"], { encoding : 'utf8' });
console.log("Process finished.");
if(child.error) {
    console.log("ERROR: ",child.error);
}
console.log("stdout: ",child.stdout);
console.log("stderr: ",child.stderr);
console.log("exist code: ",child.status);
Katie
fuente
11
+1, esta debería elegirse como la respuesta correcta ahora. Solo una nota, la variable de datos en la devolución de llamada viene como objeto Buffer. Puede usar child.stdout.setEncoding('utf8')si desea que entren cadenas utf8.
Ashish
2
Esto no funciona si necesita la información de stdoutforma asincrónica, es decir, mientras el programa restante continúa, si el proceso continúa.
Christian Hujer
2
¡Hola @ChristianHujer! Actualicé mi respuesta para incluir tanto asincrónica como sincronizada: D
Katie
si tiene un script que es: console.log("Output 1"); console.error("Boom"); console.log("Output 2");y estoy haciendo spawnAsync('node ./script.js')... ¿cómo conserva el orden de la salida? Mi salida siempre parece aparecer en el orden incorrecto.
Bryan Ray
Para su información, es aún más fácil si utiliza pipeo pipelineo pasa en las opciones correspondientes a spawn.
RichS
25

Aquí está el enfoque más limpio que he encontrado:

require("child_process").spawn('bash', ['./script.sh'], {
  cwd: process.cwd(),
  detached: true,
  stdio: "inherit"
});
Harel Ashwal
fuente
15
¿Qué está haciendo exactamente? ¿Por qué funciona? ¿Por qué es este el enfoque más limpio?
pasa el
16

Tuve un pequeño problema para obtener la salida de registro del comando "npm install" cuando generé npm en un proceso secundario. El registro en tiempo real de las dependencias no se mostró en la consola principal.

La forma más sencilla de hacer lo que quiere el póster original parece ser esta (generar npm en Windows y registrar todo en la consola principal):

var args = ['install'];

var options = {
    stdio: 'inherit' //feed all child process logging into parent process
};

var childProcess = spawn('npm.cmd', args, options);
childProcess.on('close', function(code) {
    process.stdout.write('"npm install" finished with code ' + code + '\n');
});
agenaille
fuente
3

Me encontré requiriendo esta funcionalidad con la suficiente frecuencia que la empaqueté en una biblioteca llamada std-pour . Debería permitirle ejecutar un comando y ver la salida en tiempo real. Para instalar simplemente:

npm install std-pour

Entonces es bastante simple ejecutar un comando y ver el resultado en tiempo real:

const { pour } = require('std-pour');
pour('ping', ['8.8.8.8', '-c', '4']).then(code => console.log(`Error Code: ${code}`));

Se basa en la promesa para que pueda encadenar varios comandos. Incluso es compatible con la firma de funciones, por child_process.spawnlo que debería ser un reemplazo directo en cualquier lugar donde lo esté usando.

Joel B
fuente
1
@KodieGrantham ¡me alegro de que te esté funcionando! Parece que están haciendo un trabajo genial, así que espero que los mantenga en funcionamiento.
Joel B
1

niño:

setInterval(function() {
    process.stdout.write("hi");
}, 1000); // or however else you want to run a timer

padre:

require('child_process').fork('./childfile.js');
// fork'd children use the parent's stdio
Sandro Pasquali
fuente
1

Passthru similar a PHP

import { spawn } from 'child_process';

export default async function passthru(exe, args, options) {
    return new Promise((resolve, reject) => {
        const env = Object.create(process.env);
        const child = spawn(exe, args, {
            ...options,
            env: {
                ...env,
                ...options.env,
            },
        });
        child.stdout.setEncoding('utf8');
        child.stderr.setEncoding('utf8');
        child.stdout.on('data', data => console.log(data));
        child.stderr.on('data', data => console.log(data));
        child.on('error', error => reject(error));
        child.on('close', exitCode => {
            console.log('Exit code:', exitCode);
            resolve(exitCode);
        });
    });
}

Uso

const exitCode = await passthru('ls', ['-al'], { cwd: '/var/www/html' })
Invitado
fuente
0

Agregar una respuesta relacionada con child_process.execya que yo también había necesitado comentarios en vivo y no recibí ninguno hasta que terminó el guión. Esto también complementa mi comentario a la respuesta aceptada, pero como está formateado será un poco más comprensible y más fácil de leer.

Básicamente, tengo un script npm que llama a Gulp, invocando una tarea que posteriormente se usa child_process.execpara ejecutar un script bash o por lotes según el sistema operativo. Cualquier script ejecuta un proceso de compilación a través de Gulp y luego realiza algunas llamadas a algunos binarios que funcionan con la salida de Gulp.

Es exactamente como los demás (spawn, etc.), pero en aras de la finalización, aquí se explica exactamente cómo hacerlo:

// INCLUDES
import * as childProcess from 'child_process'; // ES6 Syntax


// GLOBALS
let exec = childProcess.exec; // Or use 'var' for more proper 
                              // semantics, though 'let' is 
                              // true-to-scope


// Assign exec to a variable, or chain stdout at the end of the call
// to exec - the choice, yours (i.e. exec( ... ).stdout.on( ... ); )
let childProcess = exec
(
    './binary command -- --argument argumentValue',
    ( error, stdout, stderr ) =>
    {
        if( error )
        {
            // This won't show up until the process completes:
            console.log( '[ERROR]: "' + error.name + '" - ' + error.message );
            console.log( '[STACK]: ' + error.stack );

            console.log( stdout );
            console.log( stderr );
            callback();            // Gulp stuff
            return;
        }

        // Neither will this:
        console.log( stdout );
        console.log( stderr );
        callback();                // Gulp stuff
    }
);

Ahora es tan simple como agregar un detector de eventos. Para stdout:

childProcess.stdout.on
(
    'data',
    ( data ) =>
    {
        // This will render 'live':
        console.log( '[STDOUT]: ' + data );
    }
);

Y para stderr:

childProcess.stderr.on
(
    'data',
    ( data ) =>
    {
        // This will render 'live' too:
        console.log( '[STDERR]: ' + data );
    }
);

No está nada mal - HTH

Rik
fuente