Node.js spawnuje proces potomny i wyświetla dane wyjściowe terminala


113

Mam skrypt, który wysyła „cześć”, śpi na sekundę, wysyła „cześć”, śpi na 1 sekundę i tak dalej i tak dalej. Teraz pomyślałem, że będę mógł rozwiązać ten problem za pomocą tego modelu.

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

temp.stdout.pipe(process.stdout);

Teraz problem polega na tym, że zadanie musi zostać zakończone, aby dane wyjściowe zostały wyświetlone. Jak rozumiem, wynika to z faktu, że nowo utworzony proces przejmuje kontrolę nad wykonaniem. Oczywiście node.js nie obsługuje wątków, więc jakieś rozwiązania? Mój pomysł polegał na tym, aby ewentualnie uruchomić dwie instancje, pierwszą w określonym celu tworzenia zadania i skierować dane wyjściowe do procesu drugiej instancji, biorąc pod uwagę, że można to osiągnąć.


1
Jeśli proces potomny jest zapisany python, nie zapomnij przekazać -uflagi, aby nie buforował wyjścia konsoli, w przeciwnym razie będzie wyglądać tak, jakby skrypt nie był
aktywny

Użyj npmjs.com/package/cross-spawn zamiast czegokolwiek innego. Po prostu lepiej.
Andrew Koster

Odpowiedzi:


93

Wciąż jestem mokry w Node.js, ale mam kilka pomysłów. po pierwsze, uważam, że execFilezamiast spawn; execFiledotyczy sytuacji, gdy masz ścieżkę do skryptu, podczas gdyspawn służy do wykonywania dobrze znanego polecenia, które Node.js może rozwiązać na ścieżce systemowej.

1. Podaj wywołanie zwrotne w celu przetworzenia buforowanego wyjścia:

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. Dodaj odbiornik do strumienia wyjściowego procesu podrzędnego ( 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()); 
});

Co więcej, wydaje się, że istnieją opcje, dzięki którym można odłączyć spawnowany proces od terminala sterującego Node, co pozwoliłoby mu działać asynchronicznie. Nie testowałem tego jeszcze, ale w dokumentacji API są przykłady, które wyglądają mniej więcej tak:

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
Dodatkowe punkty, jeśli możesz wyjaśnić, jak to zrobić za pomocą exec (), ponieważ muszę wykonać polecenie cmd powłoki.
DynamicDan,

2
Możesz używać child.spawn()z shellopcją ustawioną na true. nodejs.org/api/…
CedX

5
Możesz również przesłać link child.stdout bezpośrednio do process.stdout za pomocąchild.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

Teraz jest o wiele łatwiej (6 lat później)!

Spawn zwraca obiekt childObject , za pomocą którego można następnie nasłuchiwać zdarzeń . Wydarzenia to:

  • Klasa: ChildProcess
    • Zdarzenie: „błąd”
    • Wydarzenie: „wyjście”
    • Wydarzenie: „zamknij”
    • Zdarzenie: „odłącz”
    • Wydarzenie: „wiadomość”

Istnieje również kilka obiektów z childObject , są to:

  • Klasa: ChildProcess
    • child.stdin
    • child.stdout
    • child.stderr
    • child.stdio
    • child.pid
    • child.connected
    • child.kill ([sygnał])
    • child.send (message [, sendHandle] [, callback])
    • child.disconnect ()

Zobacz więcej informacji na temat childObject: https://nodejs.org/api/child_process.html

Asynchroniczny

Jeśli chcesz uruchomić proces w tle, gdy węzeł nadal może działać, użyj metody asynchronicznej. Nadal możesz zdecydować się na wykonywanie akcji po zakończeniu procesu i gdy proces ma jakiekolwiek dane wyjściowe (na przykład, jeśli chcesz wysłać dane wyjściowe skryptu do klienta).

child_process.spawn (...); (Węzeł 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);
});

Oto jak użyłbyś metody callback + asynchronicznej :

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

Korzystając z powyższej metody, możesz wysłać każdy wiersz danych wyjściowych ze skryptu do klienta (na przykład za pomocą Socket.io do wysłania każdego wiersza, gdy otrzymujesz zdarzenia na stdoutlub stderr).

Synchroniczny

Jeśli chcesz, aby węzeł zatrzymał to, co robi i poczekał, aż skrypt się zakończy , możesz użyć wersji synchronicznej:

child_process.spawnSync (...);(Węzeł v0.11.12 +)

Problemy z tą metodą:

  • Jeśli wykonanie skryptu zajmie trochę czasu, serwer zawiesi się na ten czas!
  • Wyjście standardowe zostanie zwrócone dopiero po zakończeniu działania skryptu . Ponieważ jest synchroniczny, nie może być kontynuowany do zakończenia bieżącej linii. Dlatego nie jest w stanie przechwycić zdarzenia „stdout” do czasu zakończenia linii odradzania.

Jak tego użyć:

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, to powinno być teraz wybrane jako właściwa odpowiedź. Uwaga, zmienna data w wywołaniu zwrotnym pojawia się jako obiekt Buffer. Możesz użyć, child.stdout.setEncoding('utf8')jeśli chcesz, aby pojawiły się struny utf8.
Ashish

2
To nie działa, jeśli potrzebujesz informacji z stdoutasynchronicznie, to znaczy, gdy pozostały program kontynuuje, jeśli proces jest kontynuowany.
Christian Hujer

2
Hej @ChristianHujer! Zaktualizowałem odpowiedź, aby uwzględnić zarówno async, jak i synchronizację: D
Katie,

jeśli masz skrypt, to jest: console.log("Output 1"); console.error("Boom"); console.log("Output 2");a ja robię spawnAsync('node ./script.js')... jak zachowujesz kolejność wyników? Wydaje się, że moje wyniki zawsze pojawiają się w niewłaściwej kolejności.
Bryan Ray

FYI, jest to jeszcze łatwiejsze, jeśli używasz pipelub pipelinelub przechodzą w odpowiednich opcji do spawn.
RichS

25

Oto najczystsze podejście, jakie znalazłem:

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

15
Co dokładnie robi? Dlaczego to działa? Dlaczego jest to czystsze podejście?
Raisinrising

16

Miałem mały problem z uzyskaniem danych wyjściowych rejestrowania z polecenia „npm install”, gdy utworzyłem npm w procesie potomnym. Rejestrowanie zależności w czasie rzeczywistym nie było wyświetlane w konsoli nadrzędnej.

Najprostszym sposobem zrobienia tego, czego chce oryginalny plakat, wydaje się być to (odradzaj npm w systemie Windows i loguj wszystko do konsoli nadrzędnej):

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

Zauważyłem, że wymagam tej funkcji na tyle często, że spakowałem ją do biblioteki o nazwie std-pour . Powinien umożliwiać wykonanie polecenia i przeglądanie wyników w czasie rzeczywistym. Aby zainstalować po prostu:

npm install std-pour

Wtedy wystarczy wykonać polecenie i zobaczyć wynik w czasie rzeczywistym:

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

Jest oparty na obietnicy, dzięki czemu możesz łączyć wiele poleceń. Jest nawet zgodny z sygnaturą funkcji, child_process.spawnwięc powinien być zamiennikiem wszędzie tam, gdzie go używasz.


1
@KodieGrantham cieszę się, że działa dla Ciebie! Wygląda na to, że wykonujesz fajną robotę, więc mam nadzieję, że dzięki temu będziesz biegać.
Joel B

1

dziecko:

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

rodzic:

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

1

Passthru podobne do 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);
        });
    });
}

Stosowanie

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

0

Dodanie odpowiedzi związanej z child_process.exec jak ja również, potrzebowało informacji zwrotnej na żywo i nie otrzymałem żadnej przed zakończeniem scenariusza. To również uzupełnia mój komentarz do zaakceptowanej odpowiedzi, ale sformatowany będzie nieco bardziej zrozumiały i łatwiejszy do odczytania.

Zasadniczo mam skrypt npm, który wywołuje Gulp, wywołując zadanie, które następnie używa child_process.execdo wykonania skryptu bash lub wsadowego w zależności od systemu operacyjnego. Każdy skrypt uruchamia proces kompilacji za pośrednictwem Gulp, a następnie wykonuje wywołania niektórych plików binarnych, które współpracują z wyjściem Gulp.

Jest dokładnie tak samo jak inne (spawn itp.), Ale ze względu na ukończenie, oto dokładnie, jak to zrobić:

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

Teraz jest to tak proste, jak dodanie detektora zdarzeń. Dla stdout:

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

I dla stderr:

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

Wcale nieźle - HTH

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.