Node.js génère un processus enfant et obtient la sortie du terminal en direct

113

J'ai un script qui produit «salut», dort pendant une seconde, sort «salut», dort pendant 1 seconde, et ainsi de suite. Maintenant, je pensais que je serais capable de résoudre ce problème avec ce modèle.

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

temp.stdout.pipe(process.stdout);

Maintenant, le problème est que la tâche doit être terminée pour que la sortie soit affichée. D'après ce que je comprends, cela est dû au fait que le processus nouvellement engendré prend le contrôle de l'exécution. De toute évidence, node.js ne prend pas en charge les threads, donc des solutions? Mon idée était d'exécuter éventuellement deux instances, la première dans le but spécifique de créer la tâche et de la faire diriger la sortie vers le processus de la deuxième instance, étant donné que cela peut être réalisé.

foklepoint
la source
1
Si le processus enfant est écrit, pythonn'oubliez pas de passer le -udrapeau pour qu'il ne tamponne pas la sortie de la console, sinon il semblera que le script n'est pas en direct stackoverflow.com/a/49947671/906265
Aivaras
Utilisez npmjs.com/package/cross-spawn au lieu de toute autre chose. C'est juste mieux.
Andrew Koster

Réponses:

93

Je me mouille toujours les pieds avec Node.js, mais j'ai quelques idées. d'abord, je crois que vous devez utiliser à la execFileplace de spawn; execFileest pour lorsque vous avez le chemin vers un script, alors que spawnpour exécuter une commande bien connue que Node.js peut résoudre par rapport à votre chemin système.

1. Fournissez un rappel pour traiter la sortie mise en mémoire tampon:

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. Ajoutez un écouteur au flux stdout du processus enfant ( 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()); 
});

En outre, il semble y avoir des options par lesquelles vous pouvez détacher le processus généré du terminal de contrôle de Node, ce qui lui permettrait de fonctionner de manière asynchrone. Je n'ai pas encore testé cela, mais il existe des exemples dans la documentation de l' API qui ressemblent à ceci:

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()); 
});
RubyMardiDONO
la source
8
Des points bonus si vous pouvez expliquer comment faire cela avec exec () car j'ai besoin d'exécuter un shell cmd.
DynamicDan
2
Vous pouvez utiliser child.spawn()avec l' shelloption définie sur true. nodejs.org/api/…
CedX
5
Vous pouvez également diriger child.stdout directement vers process.stdout avecchild.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

C'est beaucoup plus facile maintenant (6 ans plus tard)!

Spawn renvoie un childObject , avec lequel vous pouvez ensuite écouter les événements . Les événements sont:

  • Classe: ChildProcess
    • Événement: 'erreur'
    • Événement: 'exit'
    • Événement: 'fermer'
    • Événement: 'déconnecter'
    • Événement: 'message'

Il existe également un tas d' objets de childObject , ce sont:

  • Classe: ChildProcess
    • enfant.stdin
    • child.stdout
    • child.stderr
    • child.stdio
    • child.pid
    • enfant.connecté
    • child.kill ([signal])
    • child.send (message [, sendHandle] [, rappel])
    • child.disconnect ()

Voir plus d'informations ici sur childObject: https://nodejs.org/api/child_process.html

Asynchrone

Si vous souhaitez exécuter votre processus en arrière-plan pendant que le nœud est toujours en mesure de continuer à s'exécuter, utilisez la méthode asynchrone. Vous pouvez toujours choisir d'effectuer des actions une fois votre processus terminé et lorsque le processus a une sortie (par exemple, si vous souhaitez envoyer la sortie d'un script au client).

child_process.spawn (...); (Nœud 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);
});

Voici comment vous utiliseriez une méthode de rappel + asynchrone :

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

En utilisant la méthode ci-dessus, vous pouvez envoyer chaque ligne de sortie du script au client (par exemple en utilisant Socket.io pour envoyer chaque ligne lorsque vous recevez des événements sur stdoutou stderr).

Synchrone

Si vous voulez que node arrête ce qu'il fait et attend que le script se termine , vous pouvez utiliser la version synchrone:

child_process.spawnSync (...); (Nœud v0.11.12 +)

Problèmes avec cette méthode:

  • Si le script prend un certain temps à se terminer, votre serveur se bloquera pendant ce laps de temps!
  • Le stdout ne sera retourné qu'une fois le script terminé . Comme il est synchrone, il ne peut pas continuer tant que la ligne actuelle n'est pas terminée. Par conséquent, il ne peut pas capturer l'événement 'stdout' tant que la ligne de spawn n'est pas terminée.

Comment l'utiliser:

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
la source
11
+1, cela devrait être choisi comme la bonne réponse maintenant. Juste une note, la variable de données dans le rappel vient en tant qu'objet Buffer. Vous pouvez utiliser child.stdout.setEncoding('utf8')si vous voulez que les chaînes utf8 arrivent.
Ashish
2
Cela ne fonctionne pas si vous avez besoin des informations de stdoutmanière asynchrone, c'est-à-dire pendant que le programme restant continue, si le processus se poursuit.
Christian Hujer
2
Salut @ChristianHujer! J'ai mis à jour ma réponse pour inclure à la fois async et sync: D
Katie
si vous avez un script qui est: console.log("Output 1"); console.error("Boom"); console.log("Output 2");et je fais spawnAsync('node ./script.js')... comment préservez-vous l'ordre de la sortie? Ma sortie semble toujours sortir dans le mauvais ordre.
Bryan Ray le
Pour info, c'est encore plus facile si vous utilisez pipeou pipelineou passez les options appropriées à spawn.
RichS
25

Voici l'approche la plus propre que j'ai trouvée:

require("child_process").spawn('bash', ['./script.sh'], {
  cwd: process.cwd(),
  detached: true,
  stdio: "inherit"
});
Harel Ashwal
la source
15
Que fait-il exactement? Pourquoi ça marche? Pourquoi est-ce une approche plus propre?
raisinrising
16

J'ai eu un peu de mal à obtenir la sortie de journalisation de la commande "npm install" lorsque j'ai engendré npm dans un processus enfant. La journalisation en temps réel des dépendances ne s'affichait pas dans la console parent.

La façon la plus simple de faire ce que l'affiche originale veut semble être la suivante (générer npm sur Windows et tout enregistrer sur la console parent):

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
la source
3

Je me suis retrouvé à avoir besoin de cette fonctionnalité assez souvent pour que je l'ai intégrée dans une bibliothèque appelée std-pour . Il devrait vous permettre d'exécuter une commande et d'afficher la sortie en temps réel. Pour installer simplement:

npm install std-pour

Ensuite, il est assez simple d'exécuter une commande et de voir la sortie en temps réel:

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

Il est promis, vous pouvez donc enchaîner plusieurs commandes. Il est même compatible avec les signatures de fonction child_process.spawn, il devrait donc être remplacé partout où vous l'utilisez.

Joël B
la source
1
@KodieGrantham heureux que cela fonctionne pour vous! Vous avez tous l'air de faire du bon travail alors j'espère que cela vous permet de continuer à courir.
Joel B du
1

enfant:

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

parent:

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

Passthru de type 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);
        });
    });
}

Usage

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

Ajout d'une réponse liée au fait child_process.execque j'avais moi aussi besoin de commentaires en direct et que je n'en recevais aucune avant la fin du script. Cela complète également mon commentaire à la réponse acceptée, mais comme il est formaté, il sera un peu plus compréhensible et plus facile à lire.

Fondamentalement, j'ai un script npm qui appelle Gulp, invoquant une tâche qui utilise ensuite child_process.execpour exécuter un script bash ou batch en fonction du système d'exploitation. Les deux scripts exécutent un processus de construction via Gulp, puis font des appels à certains binaires qui fonctionnent avec la sortie de Gulp.

C'est exactement comme les autres (spawn, etc.), mais dans un souci d'achèvement, voici exactement comment le faire:

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

Maintenant, c'est aussi simple que d'ajouter un écouteur d'événement. Pour stdout:

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

Et pour stderr:

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

Pas trop mal du tout - HTH

Rik
la source