Node.js genera un processo figlio e attiva l'output del terminale


113

Ho uno script che emette "ciao", dorme per un secondo, emette "ciao", dorme per 1 secondo e così via. Ora pensavo di poter affrontare questo problema con questo modello.

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

temp.stdout.pipe(process.stdout);

Ora il problema è che l'attività deve essere completata per visualizzare l'output. A quanto mi risulta, ciò è dovuto al fatto che il processo appena generato assume il controllo dell'esecuzione. Ovviamente node.js non supporta i thread quindi qualche soluzione? La mia idea era quella di eseguire possibilmente due istanze, la prima per lo scopo specifico di creare l'attività e di convogliare l'output al processo della seconda istanza, considerando che ciò può essere ottenuto.


1
Se il processo figlio è scritto python, non dimenticare di passare il -uflag per non bufferizzare l'output della console, altrimenti sembrerà che lo script non sia live stackoverflow.com/a/49947671/906265
Aivaras

Usa npmjs.com/package/cross-spawn invece di qualsiasi altra cosa. È solo meglio.
Andrew Koster

Risposte:


92

Mi sto ancora bagnando i piedi con Node.js, ma ho alcune idee. in primo luogo, credo che sia necessario utilizzare al execFileposto di spawn; execFileè per quando hai il percorso per uno script, mentrespawn è per eseguire un comando ben noto che Node.js può risolvere sul tuo percorso di sistema.

1. Fornire una richiamata per elaborare l'output memorizzato nel buffer:

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. Aggiungi un listener allo stream stdout del processo figlio ( 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()); 
});

Inoltre, sembrano esserci opzioni in base alle quali è possibile scollegare il processo generato dal terminale di controllo di Node, il che gli consentirebbe di funzionare in modo asincrono. Non l'ho ancora testato, ma ci sono esempi nei documenti dell'API che funzionano in questo modo:

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

8
Punti bonus se puoi spiegare come farlo con exec () poiché ho bisogno di eseguire un cmd di shell.
DynamicDan

2
È possibile utilizzare child.spawn()con l' shellopzione impostata su true. nodejs.org/api/…
CedX

5
Puoi anche reindirizzare child.stdout direttamente 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

È molto più facile ora (6 anni dopo)!

Spawn restituisce un childObject , con il quale puoi quindi ascoltare gli eventi . Gli eventi sono:

  • Classe: ChildProcess
    • Evento: "errore"
    • Evento: "uscita"
    • Evento: "chiudi"
    • Evento: "disconnect"
    • Evento: "messaggio"

Ci sono anche un sacco di oggetti da childObject , sono:

  • Classe: ChildProcess
    • child.stdin
    • child.stdout
    • child.stderr
    • child.stdio
    • child.pid
    • child.connected
    • child.kill ([segnale])
    • child.send (messaggio [, sendHandle] [, callback])
    • child.disconnect ()

Ulteriori informazioni su childObject sono disponibili qui: https://nodejs.org/api/child_process.html

asincrono

Se desideri eseguire il processo in background mentre il nodo è ancora in grado di continuare a essere eseguito, utilizza il metodo asincrono. È comunque possibile scegliere di eseguire azioni dopo il completamento del processo e quando il processo ha un output (ad esempio se si desidera inviare l'output di uno script al client).

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

Ecco come useresti un callback + metodo asincrono :

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

Utilizzando il metodo sopra, è possibile inviare ogni riga di output dallo script al client (ad esempio utilizzando Socket.io per inviare ogni riga quando si ricevono eventi su stdoutostderr ).

Sincrono

Se vuoi che il nodo interrompa ciò che sta facendo e attendi fino al completamento dello script , puoi utilizzare la versione sincrona:

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

Problemi con questo metodo:

  • Se il completamento dello script richiede un po 'di tempo, il tuo server si bloccherà per quel lasso di tempo!
  • Lo stdout verrà restituito solo al termine dell'esecuzione dello script . Poiché è sincrono, non può continuare fino al termine della riga corrente. Pertanto non è in grado di catturare l'evento "stdout" finché la linea di spawn non è terminata.

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

11
+1, questo dovrebbe essere scelto come risposta giusta ora. Solo una nota, la variabile di dati nel callback arriva come oggetto Buffer. Puoi usare child.stdout.setEncoding('utf8')se vuoi che arrivino le stringhe utf8.
Ashish

2
Questo non funziona se hai bisogno delle informazioni in stdoutmodo asincrono, cioè mentre il programma rimanente continua, se il processo continua.
Christian Hujer

2
Hey @ChristianHujer! Ho aggiornato la mia risposta per includere sia async che sync: D
Katie

se hai uno script che è: console.log("Output 1"); console.error("Boom"); console.log("Output 2");e sto facendo spawnAsync('node ./script.js')... come conservi l'ordine dell'output? La mia uscita sembra sempre uscire nell'ordine sbagliato.
Bryan Ray

Cordiali saluti, è ancora più facile se usi pipeo pipelineo passi le opzioni appropriate a spawn.
RichS

25

Ecco l'approccio più pulito che ho trovato:

require("child_process").spawn('bash', ['./script.sh'], {
  cwd: process.cwd(),
  detached: true,
  stdio: "inherit"
});

15
Cosa sta facendo, esattamente? Perché funziona? Perché questo è l'approccio più pulito?
Raisinrising

16

Ho avuto qualche problema a ottenere l'output di registrazione dal comando "npm install" quando ho generato npm in un processo figlio. La registrazione in tempo reale delle dipendenze non è stata visualizzata nella console principale.

Il modo più semplice per fare ciò che vuole il poster originale sembra essere questo (generare npm su Windows e registrare tutto sulla console genitore):

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

3

Mi sono ritrovato a richiedere questa funzionalità abbastanza spesso da inserirla in una libreria chiamata std-pour . Dovrebbe consentire di eseguire un comando e visualizzare l'output in tempo reale. Per installare semplicemente:

npm install std-pour

Quindi è abbastanza semplice eseguire un comando e vedere l'output in tempo reale:

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

È promesso basato in modo da poter concatenare più comandi. È anche compatibile con la firma della funzione, child_process.spawnquindi dovrebbe essere una sostituzione ovunque tu lo usi.


1
@KodieGrantham felice che funzioni per te! Sembra che stiate facendo un ottimo lavoro, quindi spero che vi faccia continuare a correre.
Joel B

1

bambino:

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

genitore:

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

1

Passante simile 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' })

0

Aggiunta di una risposta relativa a child_process.exec come anch'io avevo bisogno di feedback dal vivo e non ne ottenevo fino a dopo la fine della sceneggiatura. Questo integra anche il mio commento alla risposta accettata, ma poiché è formattato sarà un po 'più comprensibile e più facile da leggere.

Fondamentalmente, ho uno script npm che chiama Gulp, invocando un'attività che successivamente utilizza child_process.execper eseguire uno script bash o batch a seconda del sistema operativo. Uno script esegue un processo di compilazione tramite Gulp e quindi effettua alcune chiamate ad alcuni binari che funzionano con l'output di Gulp.

È esattamente come gli altri (spawn, ecc.), Ma per il completamento, ecco esattamente come farlo:

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

Ora è semplice come aggiungere un listener di eventi. Per stdout:

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

E per stderr:

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

Non male per niente - HTH

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.