Exec: affiche stdout «en direct»


198

J'ai ce script simple:

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

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

où j'exécute simplement une commande pour compiler un fichier coffee-script. Mais stdout ne s'affiche jamais dans la console, car la commande ne se termine jamais (à cause de l'option -w de coffee). Si j'exécute la commande directement depuis la console, je reçois un message comme celui-ci:

18:05:59 - compiled my_file.coffee

Ma question est: est-il possible d'afficher ces messages avec l'exec de node.js? Si oui comment? !

Merci


1
Je suis venu ici à la recherche de capture stdout à partir de l'exécutable Python. Notez que tout ce qui suit fonctionnera, mais vous devez exécuter python avec une option "-u", pour ne pas être tamponné et ainsi avoir des mises à jour en direct.
Andy

Réponses:


274

N'utilisez pas exec. Utilisez spawnqui est un EventEmmiterobjet. Ensuite, vous pouvez écouter stdout/ stderrevents ( spawn.stdout.on('data',callback..)) au fur et à mesure qu'ils se produisent .

À partir de la documentation 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 met en mémoire tampon la sortie et la renvoie généralement lorsque la commande a fini de s'exécuter.


25
Très agréable. Pour info: l'argument de rappel des événements stdout / stderr 'data' est un tampon, alors appelez-le avec .toString ()
SergeL

4
Pour ceux d'entre vous qui ne peuvent pas faire fonctionner spawn sous Windows, jetez un œil à cette excellente réponse .
tomekwi

22
exec est également un EventEmitter au moins en dernier.
Nikolay Tsenkov le

4
Gardez également à l'esprit que le rappel ne sera pas appelé chaque fois que le programme sortira une nouvelle ligne. Si vous souhaitez recevoir des «événements» du processus enfant, ce processus doit vider le tampon ( flush(stdout);en C) afin de déclencher des événements dans Node.js.
Julian F. Weinert

7
+1 sur exec étant également un EventEmitter .. a passé 2 heures à refactoriser ma chaîne dans un tableau args (ligne de commande ffmpeg très longue et compliquée) .. seulement pour découvrir que je n'en avais pas vraiment besoin.
deadconversations

185

exec renverra également un objet ChildProcess qui est 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); 
});

OU pipele stdout du processus enfant vers le stdout principal.

coffeeProcess.stdout.pipe(process.stdout);

OU hériter de stdio en utilisant spawn

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

35
On dirait que cela peut être simplifié en utilisant simplement pipe:coffeeProcess.stdout.pipe(process.stdout);
Eric Freese

3
Le commentaire de @ EricFreese est ce que je recherchais, car je voulais tirer parti de la fonction de remplacement des personnages de stdout (harnais un rapporteur dans un script de nœud)
LoremIpsum

19
Simple: spawn(cmd, argv, { stdio: 'inherit' }). Voir nodejs.org/api/child_process.html#child_process_options_stdio pour différents exemples.
Morgan Touverey Quilling

3
+1 pour la suggestion de @ MorganTouvereyQuilling à utiliser spawnavec stdio: 'inherit'. Il produit une sortie plus précise que execet piping stdout/ stderr, par exemple lors de l'affichage des informations de progression d'un fichier git clone.
Livven

62

Il existe déjà plusieurs réponses, mais aucune d'entre elles ne mentionne la meilleure (et la plus simple) façon de le faire, à savoir l'utilisation spawnet l' { stdio: 'inherit' }option . Il semble produire la sortie la plus précise, par exemple lors de l'affichage des informations de progression à partir d'un fichier git clone.

Faites simplement ceci:

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

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

Merci à @MorganTouvereyQuilling pour l'avoir signalé dans ce commentaire .


1
J'ai constaté que lorsque le sous-processus utilise une sortie formatée comme du texte coloré, il stdio: "inherit"préserve ce formatage alors que ce child.stdout.pipe(process.stdout)n'est pas le cas.
Rikki Gibson

Cela préserve parfaitement la sortie même sur les processus avec une sortie complexe comme les barres de progression sur les installations npm. Impressionnant!
Dave Koo

1
pourquoi ce n'est pas la réponse acceptée? c'est le seul qui a fonctionné pour moi et c'est juste 2 lignes f * !!!
Lincoln

Cette astuce a été utile lors de l'exécution de certaines applications de ligne de commande Symfony qui utilisent des barres de progression. À votre santé.
Halfstop du

Cela devrait être la réponse acceptée - seule chose qui préserve une représentation de sortie parfaite et c'est la plus simple? oui s'il vous plaît
evnp

23

Inspiré de la réponse de Nathanael Smith et du commentaire d'Eric Freese, cela pourrait être aussi simple que:

var exec = require('child_process').exec;
exec('coffee -cw my_file.coffee').stdout.pipe(process.stdout);

Cela semble fonctionner correctement pour des commandes simples comme lsmais échoue pour des commandes plus complexes telles que npm install. J'ai même essayé de raccorder à la fois stdout et stderr à leurs objets de processus respectifs.
linuxdan

@linuxdan c'est peut-être parce que npm écrit dans stderr (j'en ai vu certains écrire la barre de progression). vous pouvez également canaliser stderr, ou étendre la solution Tongfa pour écouter sur stderr.
Sergiu

@linuxdan D'après ce que j'ai vu, le moyen le plus fiable est spawn(command, args, { stdio: 'inherit' }), comme suggéré ici, stackoverflow.com/questions/10232192/…
Livven

Meilleure réponse, merci pour cela. A travaillé comme un charme
Abhishek Sharma

21

Je voudrais simplement ajouter qu'un petit problème avec la sortie des chaînes de tampon à partir d'un processus généré avec console.log()est qu'il ajoute des nouvelles lignes, ce qui peut répartir la sortie de votre processus généré sur des lignes supplémentaires. Si vous sortez stdoutou stderravec process.stdout.write()au lieu de console.log(), alors vous obtiendrez la sortie de la console du processus généré «tel quel».

J'ai vu cette solution ici: Node.js: impression sur console sans nouvelle ligne de fin?

J'espère que cela aidera quelqu'un à utiliser la solution ci-dessus (qui est excellente pour la sortie en direct, même si elle provient de la documentation).


1
Pour une utilisation de sortie encore plus précise spawn(command, args, { stdio: 'inherit' }), comme suggéré par @MorganTouvereyQuilling ici stackoverflow.com/questions/10232192/…
Livven

12

J'ai trouvé utile d'ajouter un script d'exécution personnalisé à mes utilitaires qui le font.

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

5

Après avoir examiné toutes les autres réponses, je me suis retrouvé avec ceci:

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

Parfois, il y dataaura plusieurs lignes, donc l'en- oldSchoolMakeBuildtête apparaîtra une fois pour plusieurs lignes. Mais cela ne m'a pas assez dérangé pour le changer.


3

child_process.spawn renvoie un objet avec les flux stdout et stderr. Vous pouvez appuyer sur le flux stdout pour lire les données que le processus enfant renvoie à Node. stdout étant un flux a les "données", la "fin" et d'autres événements que les flux ont. spawn est mieux utilisé lorsque vous souhaitez que le processus enfant renvoie une grande quantité de données à Node - traitement d'image, lecture de données binaires, etc.

vous pouvez donc résoudre votre problème en utilisant child_process.spawn comme ci-dessous.

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

1

Voici une fonction d'assistance asynchrone écrite en manuscrit qui semble faire l'affaire pour moi. Je suppose que cela ne fonctionnera pas pour les processus de longue durée, mais pourrait toujours être utile pour quelqu'un?

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})
        })
    })
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.