Exec: wyświetl standardowe wyjście „na żywo”

188

Mam ten prosty skrypt:

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

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

gdzie po prostu wykonuję polecenie skompilowania pliku skryptu kawy. Ale standardowe wyjście nigdy nie jest wyświetlane w konsoli, ponieważ polecenie nigdy się nie kończy (z powodu opcji -w kawy). Jeśli wykonam polecenie bezpośrednio z konsoli, otrzymam następujący komunikat:

18:05:59 - compiled my_file.coffee

Moje pytanie brzmi: czy możliwe jest wyświetlanie tych komunikatów za pomocą exec node.js? Jeśli tak jak? !

Dzięki

mravey
źródło
1
Przybyłem tutaj, szukając przechwytywania standardowego z pliku wykonywalnego Python. Zauważ, że wszystkie poniższe elementy będą działać, ale musisz uruchomić Pythona z opcją „-u”, aby wykasować outbuforowanie i tym samym mieć bieżące aktualizacje.
Andy,

Odpowiedzi:

266

Nie używać exec. Użyj, spawnktóry jest EventEmmiterprzedmiotem. Następnie możesz słuchać stdout/ stderrevents ( spawn.stdout.on('data',callback..)), gdy się zdarzają .

Z dokumentacji 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 buforuje dane wyjściowe i zwykle zwraca je po zakończeniu wykonywania polecenia.

Pooria Azimi
źródło
22
Bardzo dobrze. FYI: Argument „data” wywołania zwrotnego zdarzeń stdout / stderr jest buforem, więc wywołaj go za pomocą .toString ()
SergeL
4
Dla tych z was, którzy nie mogą zmusić spawna do pracy w systemie Windows, spójrz na tę świetną odpowiedź .
tomekwi
17
exec jest także EventEmitter najpóźniej.
Nikolay Tsenkov
4
Należy również pamiętać, że wywołanie zwrotne nie będzie wywoływane, ilekroć program wyświetli nową linię. Jeśli chcesz odbierać „zdarzenia” z procesu potomnego, proces ten musi opróżnić bufor ( flush(stdout);w C), aby uruchomić zdarzenia w Node.js.
Julian F. Weinert,
5
+1 na exec jest także EventEmitter .. spędziłem 2 godziny na refaktoryzacji mojego łańcucha do tablicy args (bardzo długa i skomplikowana linia komend ffmpeg) .. tylko po to, aby dowiedzieć się, że tak naprawdę nie musiałem.
deadconversations
176

exec zwróci również obiekt ChildProcess, który jest EventEmitter.

var exec = require('child_process').exec;
var coffeeProcess = exec('coffee -cw my_file.coffee');

coffeeProcess.stdout.on('data', function(data) {
    console.log(data); 
});

LUB pipestdout procesu potomnego do stdout głównego.

coffeeProcess.stdout.pipe(process.stdout);

LUB odziedzicz stdio za pomocą spawn

spawn('coffee -cw my_file.coffee', { stdio: 'inherit' });
Nathanael Smith
źródło
35
Wygląda na to, że można to uprościć, używając pipe:coffeeProcess.stdout.pipe(process.stdout);
Eric Freese,
3
Komentarz EricFreese był tym, czego szukałem, ponieważ chciałem wykorzystać funkcję zamiany znaków stdout (wykorzystanie kątomierza w skrypcie węzła)
LoremIpsum
19
Prostsze: spawn(cmd, argv, { stdio: 'inherit' }). Zobacz nodejs.org/api/child_process.html#child_process_options_stdio dla różnych przykładów.
Morgan Touverey Quilling
3
+1 dla @ sugestia MorganTouvereyQuilling do korzystania spawnz stdio: 'inherit'. Daje bardziej dokładny wynik niż execi przesyłanie stdout/ stderr, na przykład podczas wyświetlania informacji o postępie z git clone.
Livven 18.04.17
57

Istnieje już kilka odpowiedzi, jednak żadna z nich nie wspomina o najlepszym (i najłatwiejszym) sposobie zrobienia tego, który jest przy użyciu spawni { stdio: 'inherit' }opcji . Wydaje się, że daje najdokładniejsze wyjście, na przykład podczas wyświetlania informacji o postępie z git clone.

Po prostu zrób to:

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

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

Podziękowania dla @MorganTouvereyQuilling za wskazanie tego w tym komentarzu .

Livven
źródło
1
Odkryłem, że gdy podproces używa sformatowanych danych wyjściowych, takich jak kolorowy tekst, stdio: "inherit"zachowuje to formatowanie, podczas gdy child.stdout.pipe(process.stdout)nie.
Rikki Gibson
To doskonale zachowuje dane wyjściowe nawet w przypadku procesów o złożonym wyniku, takich jak paski postępu w instalacjach npm. Niesamowite!
Dave Koo,
1
dlaczego nie jest to akceptowana odpowiedź? to był jedyny, który działał dla mnie i to tylko 2 f * linie !!!
Lincoln
Ta wskazówka była pomocna podczas uruchamiania niektórych aplikacji wiersza polecenia Symfony, które używają pasków postępu. Twoje zdrowie.
Halfstop
To powinna być zaakceptowana odpowiedź - jedyna rzecz, która zachowuje idealną reprezentację wyników i jest najprostsza? tak, proszę
evnp
21

Chciałbym tylko dodać, że jednym małym problemem z wyprowadzaniem ciągów buforów z odrodzonego procesu console.log()jest to, że dodaje on nowe linie, które mogą rozłożyć wyniki odrodzonego procesu na dodatkowe linie. Jeśli wyjście stdoutlub stderrze process.stdout.write()zamiast console.log()Pokochasz więc uzyskać wyjścia konsoli z zrodził proces „jak jest”.

Widziałem to rozwiązanie tutaj: Node.js: drukowanie na konsoli bez końca nowej linii?

Mam nadzieję, że pomoże to komuś, kto użyje powyższego rozwiązania (które świetnie nadaje się do transmisji na żywo, nawet jeśli pochodzi z dokumentacji).

Kevin Teljeur
źródło
1
Aby uzyskać jeszcze dokładniejsze dane wyjściowe spawn(command, args, { stdio: 'inherit' }), zgodnie z sugestią @MorganTouvereyQuilling tutaj stackoverflow.com/questions/10232192/...
Livven
21

Zainspirowany odpowiedzią Nathanael Smith i komentarzem Erica Freese'a może być tak prosty, jak:

var exec = require('child_process').exec;
exec('coffee -cw my_file.coffee').stdout.pipe(process.stdout);
Tyler Long
źródło
To wydaje się działać dobrze dla prostych poleceń, takich jak ls, ale nie w przypadku bardziej złożonych poleceń, takich jak npm install. Próbowałem nawet potokować stdout i stderr do odpowiednich obiektów procesu.
linuxdan
@linuxdan może to być spowodowane tym, że npm pisze w stderr (widziałem, jak tam napisano pasek postępu). możesz potokować także stderr lub rozszerzyć rozwiązanie Tongfa, aby słuchać na stderr.
Sergiu
@linuxdan Z tego, co widziałem, najbardziej wiarygodnym sposobem jest spawn(command, args, { stdio: 'inherit' }), jak sugerowano tutaj stackoverflow.com/questions/10232192/…
Livven
Najlepsza odpowiedź, dzięki za to. Działało jak urok
Abhishek Sharma
12

Przydało mi się dodanie niestandardowego skryptu exec do moich narzędzi, które to robią.

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
źródło
5

Po przejrzeniu wszystkich pozostałych odpowiedzi otrzymałem:

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

Czasami databędzie wiele linii, więc oldSchoolMakeBuildnagłówek pojawi się raz dla wielu linii. Ale to mi nie przeszkadzało, żeby to zmienić.

Tongfa
źródło
3

child_process.spawn zwraca obiekt ze strumieniami stdout i stderr. Możesz dotknąć strumienia standardowego, aby odczytać dane, które proces potomny odsyła z powrotem do Węzła. stdout będący strumieniem ma „dane”, „koniec” i inne zdarzenia, które mają strumienie. spawn najlepiej przydaje się, gdy chcesz, aby proces potomny zwrócił dużą ilość danych do węzła - przetwarzanie obrazu, odczyt danych binarnych itp.

abyś mógł rozwiązać swój problem za pomocą child_process.spawn, jak opisano poniżej.

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
źródło
1

Oto asynchroniczna funkcja pomocnicza napisana w pismach maszynowych, która wydaje mi się wystarczająca. Myślę, że to nie zadziała w przypadku długotrwałych procesów, ale nadal może być przydatne dla kogoś?

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})
        })
    })
}
Swaner
źródło