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

Mravey
la source
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.

Pooria Azimi
la source
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' });
Nathanael Smith
la source
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 .

Livven
la source
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);
Tyler Long
la source
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).

Kevin Teljeur
la source
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')
IanLancaster
la source
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.

Tongfa
la source
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());
});
Adeojo Emmanuel IMM
la source
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})
        })
    })
}
Cygne
la source