Node.js erzeugt einen untergeordneten Prozess und erhält die Terminalausgabe live

113

Ich habe ein Skript, das 'hi' ausgibt, eine Sekunde lang schläft, 'hi' ausgibt, 1 Sekunde lang schläft und so weiter und so fort. Jetzt dachte ich, ich könnte dieses Problem mit diesem Modell lösen.

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

temp.stdout.pipe(process.stdout);

Das Problem ist nun, dass die Aufgabe beendet werden muss, damit die Ausgabe angezeigt wird. Soweit ich weiß, liegt dies daran, dass der neu erzeugte Prozess die Ausführungskontrolle übernimmt. Offensichtlich unterstützt node.js keine Threads, also irgendwelche Lösungen? Meine Idee war es, möglicherweise zwei Instanzen auszuführen, die erste für den spezifischen Zweck des Erstellens der Aufgabe, und die Ausgabe an den Prozess der zweiten Instanz weiterzuleiten, da dies erreicht werden kann.

foklepoint
quelle
1
Wenn ein untergeordneter Prozess geschrieben pythonwird, vergessen Sie nicht, das -uFlag zu übergeben, damit die Konsolenausgabe nicht gepuffert
Aivaras
Verwenden Sie npmjs.com/package/cross-spawn anstelle von allem anderen. Es ist einfach besser.
Andrew Koster

Antworten:

92

Ich mache immer noch meine Füße nass mit Node.js, aber ich habe ein paar Ideen. Erstens glaube ich, dass Sie execFileanstelle von verwenden müssen spawn; execFileist für den Fall, dass Sie den Pfad zu einem Skript haben, während spawnfür die Ausführung eines bekannten Befehls, den Node.js für Ihren Systempfad auflösen kann.

1. Geben Sie einen Rückruf ein , um die gepufferte Ausgabe zu verarbeiten:

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. Fügen Sie dem stdout- Stream des untergeordneten Prozesses ( 9thport.net ) einen Listener hinzu.

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

Darüber hinaus scheint es Optionen zu geben, mit denen Sie den erzeugten Prozess vom steuernden Terminal des Knotens trennen können, wodurch er asynchron ausgeführt werden kann. Ich habe dies noch nicht getestet, aber es gibt Beispiele in den API-Dokumenten , die ungefähr so aussehen:

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()); 
});
RubyTuesdayDONO
quelle
8
Bonuspunkte, wenn Sie erklären können, wie dies mit exec () gemacht wird, da ich ein Shell-Cmd ausführen muss.
DynamicDan
2
Sie können child.spawn()mit der shellOption auf verwenden true. nodejs.org/api/…
CedX
5
Sie können child.stdout auch direkt an process.stdout mitchild.stdout.pipe(process.stdout);
darkadept
@ DynamicDanjavascript 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

Es ist jetzt viel einfacher (6 Jahre später)!

Spawn gibt ein childObject zurück , mit dem Sie dann auf Ereignisse warten können . Die Ereignisse sind:

  • Klasse: ChildProcess
    • Ereignis: 'Fehler'
    • Ereignis: 'Beenden'
    • Ereignis: 'schließen'
    • Ereignis: 'Verbindung trennen'
    • Ereignis: 'Nachricht'

Es gibt auch eine Reihe von Objekten aus childObject :

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

Weitere Informationen zu childObject finden Sie hier: https://nodejs.org/api/child_process.html

Asynchron

Wenn Sie Ihren Prozess im Hintergrund ausführen möchten, während der Knoten weiterhin ausgeführt werden kann, verwenden Sie die asynchrone Methode. Sie können weiterhin Aktionen ausführen, nachdem Ihr Prozess abgeschlossen wurde und wenn der Prozess eine Ausgabe hat (z. B. wenn Sie die Ausgabe eines Skripts an den Client senden möchten).

child_process.spawn (...); (Knoten 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);
});

So würden Sie eine Callback + asynchrone Methode verwenden :

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

Mit der obigen Methode können Sie jede Ausgabezeile des Skripts an den Client senden (z. B. mit Socket.io, um jede Zeile zu senden, wenn Sie Ereignisse auf stdoutoder empfangen stderr).

Synchron

Wenn Sie möchten, dass der Knoten seine Arbeit beendet und wartet, bis das Skript abgeschlossen ist , können Sie die synchrone Version verwenden:

child_process.spawnSync (...); (Knoten v0.11.12 +)

Probleme mit dieser Methode:

  • Wenn das Skript eine Weile dauert, bleibt Ihr Server für diese Zeit hängen!
  • Das stdout wird erst zurückgegeben, wenn das Skript ausgeführt wurde . Da es synchron ist, kann es nicht fortgesetzt werden, bis die aktuelle Zeile beendet ist. Daher ist es nicht möglich, das 'stdout'-Ereignis zu erfassen, bis die Spawn-Linie beendet ist.

Wie man es benutzt:

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
quelle
11
+1, dies sollte jetzt als die richtige Antwort gewählt werden. Nur eine Anmerkung, die Datenvariable im Rückruf kommt als Pufferobjekt herein. Sie können verwenden, child.stdout.setEncoding('utf8')wenn Sie möchten, dass utf8-Zeichenfolgen
Ashish
2
Dies funktioniert nicht, wenn Sie die Informationen stdoutasynchron benötigen, dh während das verbleibende Programm fortgesetzt wird, wenn der Prozess fortgesetzt wird.
Christian Hujer
2
Hey @ChristianHujer! Ich habe meine Antwort so aktualisiert, dass sie sowohl asynchron als auch synchron ist: D
Katie
Wenn Sie ein Skript haben, das lautet: console.log("Output 1"); console.error("Boom"); console.log("Output 2");und ich mache spawnAsync('node ./script.js')... wie behalten Sie die Reihenfolge der Ausgabe bei? Meine Ausgabe scheint immer in der falschen Reihenfolge zu erscheinen.
Bryan Ray
Zu Ihrer Information, ist es noch einfacher , wenn Sie verwenden pipeoder pipelineoder in den entsprechenden Optionen übergeben spawn.
RichS
25

Hier ist der sauberste Ansatz, den ich gefunden habe:

require("child_process").spawn('bash', ['./script.sh'], {
  cwd: process.cwd(),
  detached: true,
  stdio: "inherit"
});
Harel Ashwal
quelle
15
Was macht es genau? Warum funktioniert es? Warum ist dies der sauberere Ansatz?
Rosinenaufstand
16

Ich hatte ein kleines Problem damit, die Protokollausgabe des Befehls "npm install" zu erhalten, als ich npm in einem untergeordneten Prozess erzeugt habe. Die Echtzeitprotokollierung von Abhängigkeiten wurde in der übergeordneten Konsole nicht angezeigt.

Der einfachste Weg, um das zu tun, was das Originalposter will, scheint folgender zu sein (npm unter Windows erzeugen und alles in der übergeordneten Konsole protokollieren):

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
quelle
3

Ich benötigte diese Funktionalität oft genug, um sie in eine Bibliothek namens std-pour zu packen . Sie sollten damit einen Befehl ausführen und die Ausgabe in Echtzeit anzeigen können. Einfach zu installieren:

npm install std-pour

Dann ist es einfach genug, einen Befehl auszuführen und die Ausgabe in Echtzeit zu sehen:

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

Es ist versprochen, so dass Sie mehrere Befehle verketten können. Es ist sogar mit der Signatur kompatibel, child_process.spawnsodass es überall dort, wo Sie es verwenden, weniger Ersatz sein sollte.

Joel B.
quelle
1
@KodieGrantham froh, dass es für Sie funktioniert! Ihr scheint so, als ob ihr coole Arbeit macht, also hoffe ich, dass es euch am Laufen hält.
Joel B
1

Kind:

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

Elternteil:

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

PHP-ähnlicher Durchgang

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

Verwendung

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

Das Hinzufügen einer Antwort im Zusammenhang mit, child_process.execda auch ich Live-Feedback benötigt hatte und erst nach Abschluss des Skripts eine Antwort erhielt . Dies ergänzt auch meinen Kommentar zu der akzeptierten Antwort, aber da sie formatiert ist, wird sie etwas verständlicher und leichter zu lesen sein.

Grundsätzlich habe ich ein npm-Skript, das Gulp aufruft und eine Aufgabe aufruft, mit der anschließend child_process.execje nach Betriebssystem ein Bash- oder Batch-Skript ausgeführt wird. Jedes Skript führt einen Erstellungsprozess über Gulp aus und ruft dann einige Binärdateien auf, die mit der Gulp-Ausgabe funktionieren.

Es ist genau wie bei den anderen (Spawn usw.), aber der Vollständigkeit halber ist hier genau zu sehen, wie es geht:

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

Jetzt ist es so einfach wie das Hinzufügen eines Ereignis-Listeners. Für stdout:

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

Und für stderr:

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

Gar nicht so schlecht - HTH

Rik
quelle