spawn()spawn() funktioniertspawnSync()spawn()exec()execFile()spawnAsync()execSync()execFileSync()'node:child_process'In diesem Kapitel werden wir untersuchen, wie wir Shell-Befehle aus Node.js über das Modul 'node:child_process' ausführen können.
Das Modul 'node:child_process' hat eine Funktion zur Ausführung von Shell-Befehlen (in *gestarteten* Kindprozessen), die in zwei Versionen vorliegt
spawn().spawnSync().Wir werden zuerst spawn() und dann spawnSync() untersuchen. Abschließend betrachten wir die folgenden Funktionen, die auf ihnen basieren und relativ ähnlich sind
spawn()exec()execFile()spawnSync()execSync()execFileSync()Der in diesem Kapitel gezeigte Code läuft unter Unix, aber ich habe ihn auch unter Windows getestet – wo die meiste Funktionalität mit geringfügigen Änderungen funktioniert (z. B. Zeilen mit '\r\n' statt '\n' beenden).
Die folgende Funktionalität taucht oft in den Beispielen auf. Deshalb wird sie hier einmal erklärt
Assertions: assert.equal() für primitive Werte und assert.deepEqual() für Objekte. Der notwendige Import wird in den Beispielen nie gezeigt
import * as assert from 'node:assert/strict';Die Funktion Readable.toWeb() konvertiert Node's native stream.Readable in einen Web-Stream (eine Instanz von ReadableStream). Sie wird in §10 „Web-Streams unter Node.js verwenden“ erklärt. Readable wird in den Beispielen immer importiert.
Die asynchrone Funktion readableStreamToString() konsumiert einen lesbaren Web-Stream und gibt einen String zurück (eingewickelt in ein Promise). Sie wird im Kapitel über Web-Streams erklärt. Diese Funktion wird als in den Beispielen verfügbar angenommen.
spawn()spawn() funktioniertspawn(
command: string,
args?: Array<string>,
options?: Object
): ChildProcessspawn() führt einen Befehl asynchron in einem neuen Prozess aus: Der Prozess läuft parallel zum Haupt-JavaScript-Prozess von Node und wir können auf verschiedene Weise mit ihm kommunizieren (oft über Streams).
Als Nächstes folgen Dokumentationen zu den Parametern und dem Ergebnis von spawn(). Wenn Sie lieber anhand von Beispielen lernen, können Sie diese Inhalte überspringen und mit den folgenden Unterabschnitten fortfahren.
commandcommand ist ein String mit dem Shell-Befehl. Es gibt zwei Modi für die Verwendung dieses Parameters
args wird weggelassen und command enthält den gesamten Shell-Befehl. Wir können sogar Shell-Features wie Piping zwischen mehreren ausführbaren Dateien, I/O-Umleitung in Dateien, Variablen und Wildcards verwenden.options.shell muss true sein, da wir eine Shell benötigen, um die Shell-Features zu verarbeiten.command enthält nur den Namen des Befehls und args enthält seine Argumente.options.shell true ist, werden viele Metazeichen in Argumenten interpretiert und Features wie Wildcards und Variablennamen funktionieren.options.shell false ist, werden Strings wörtlich verwendet und wir müssen niemals Metazeichen escapen.Beide Modi werden später in diesem Kapitel demonstriert.
optionsDie folgenden options sind am interessantesten
.shell: boolean|string (Standard: false)true sein. Zum Beispiel können .bat und .cmd Dateien nicht anders ausgeführt werden..shell false ist..shell true ist, müssen wir bei Benutzereingaben vorsichtig sein und sie bereinigen, da es einfach ist, beliebigen Code auszuführen. Wir müssen auch Metazeichen escapen, wenn wir sie als Nicht-Metazeichen verwenden möchten..shell auch auf den Pfad einer Shell-ausführbaren Datei setzen. Dann verwendet Node.js diese ausführbare Datei, um den Befehl auszuführen. Wenn wir .shell auf true setzen, verwendet Node.js'/bin/sh'process.env.ComSpec.cwd: string | URL.stdio: Array<string|Stream>|string.env: Object (Standard: process.env)Schauen Sie sich process.env an (z. B. in der Node.js REPL), um zu sehen, welche Variablen existieren.
Wir können das Spread-Operator verwenden, um eine vorhandene Variable nicht-destruktiv zu überschreiben – oder sie zu erstellen, wenn sie noch nicht existiert
{env: {...process.env, MY_VAR: 'Hi!'}}.signal: AbortSignalac erstellen, können wir ac.signal an spawn() übergeben und den Kindprozess über ac.abort() abbrechen. Dies wird später in diesem Kapitel demonstriert..timeout: number.timeout Millisekunden dauert, wird er beendet.options.stdioJeder der Standard-I/O-Streams des Kindprozesses hat eine numerische ID, einen sogenannten *Deskriptor*
Es kann mehr Deskriptoren geben, aber das ist selten.
options.stdio konfiguriert, ob und wie die Streams des Kindprozesses mit Streams im Elternprozess verbunden werden. Es kann ein Array sein, bei dem jedes Element den Deskriptor konfiguriert, der seinem Index entspricht. Die folgenden Werte können als Array-Elemente verwendet werden
'pipe':
childProcess.stdin mit der Standardeingabe des Kindes. Beachten Sie, dass ersteres trotz seines Namens ein Stream ist, der zum Elternprozess gehört.childProcess.stdout weiter.childProcess.stderr weiter.'ignore': Ignoriert den Stream des Kindes.
'inherit': Leitet den Stream des Kindes an den entsprechenden Stream des Elternprozesses weiter.
'inherit' verwenden.Native Node.js-Stream: Leitet zu oder von diesem Stream weiter.
Es werden auch andere Werte unterstützt, aber das liegt außerhalb des Rahmens dieses Kapitels.
Anstatt options.stdio über ein Array anzugeben, können wir auch abkürzen
'pipe' ist äquivalent zu ['pipe', 'pipe', 'pipe'] (der Standard für options.stdio).'ignore' ist äquivalent zu ['ignore', 'ignore', 'ignore'].'inherit' ist äquivalent zu ['inherit', 'inherit', 'inherit'].ChildProcessspawn() gibt Instanzen von ChildProcess zurück.
Interessante Dateneigenschaften
.exitCode: number | nullnull bedeutet, dass der Prozess noch nicht beendet ist..signalCode: string | nullnull, wenn dies nicht der Fall war. Weitere Informationen finden Sie in der Beschreibung der Methode .kill() unten..stdin.stdout.stderr.pid: number | undefined.pid undefined. Dieser Wert ist unmittelbar nach dem Aufruf von spawn() verfügbar.Interessante Methoden
.kill(signalCode?: number | string = 'SIGTERM'): boolean
Sendet ein POSIX-Signal an den Kindprozess (was normalerweise zur Beendigung des Prozesses führt)
signal enthält eine Liste von Werten.SIGINT, SIGTERM und SIGKILL. Weitere Informationen finden Sie in der Node.js-Dokumentation.Diese Methode wird später in diesem Kapitel demonstriert.
Interessante Ereignisse
.on('exit', (exitCode: number|null, signalCode: string|null) => {})'close' benachrichtigt uns, wenn alle stdio-Streams nach dem Exit eines Kindprozesses geschlossen sind..on('error', (err: Error) => {})'exit'-Ereignis wird möglicherweise nach diesem Ereignis emittiert.Wir werden später sehen, wie Ereignisse in Promises umgewandelt werden können, die awaitet werden können.
Bei Verwendung des asynchronen spawn() wird der Kindprozess für den Befehl asynchron gestartet. Der folgende Code demonstriert dies
import {spawn} from 'node:child_process';
spawn(
'echo', ['Command starts'],
{
stdio: 'inherit',
shell: true,
}
);
console.log('After spawn()');Dies ist die Ausgabe
After spawn()
Command starts
In diesem Abschnitt geben wir dieselbe Befehlsaufrufung auf zwei Arten an
command bereit.command und seine Argumente über den zweiten Parameter args bereit.import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
const childProcess = spawn(
'echo "Hello, how are you?"',
{
shell: true, // (A)
stdio: ['ignore', 'pipe', 'inherit'], // (B)
}
);
const stdout = Readable.toWeb(
childProcess.stdout.setEncoding('utf-8'));
// Result on Unix
assert.equal(
await readableStreamToString(stdout),
'Hello, how are you?\n' // (C)
);
// Result on Windows: '"Hello, how are you?"\r\n'Jedes Starten im Befehlsmodus mit Argumenten erfordert, dass .shell true ist (Zeile A) – auch wenn es so einfach ist wie dieses.
In Zeile B teilen wir spawn() mit, wie die Standard-I/O behandelt werden soll
childProcess.stdout weiterleiten (ein Stream, der zum Elternprozess gehört).In diesem Fall sind wir nur an der Ausgabe des Kindprozesses interessiert. Daher sind wir fertig, sobald wir die Ausgabe verarbeitet haben. In anderen Fällen müssen wir möglicherweise warten, bis das Kind beendet wird. Wie das geht, wird später demonstriert.
Im Befehlsmodus sehen wir weitere Besonderheiten von Shells – zum Beispiel enthält die Ausgabe der Windows Command Shell doppelte Anführungszeichen (letzte Zeile).
import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
const childProcess = spawn(
'echo', ['Hello, how are you?'],
{
shell: true,
stdio: ['ignore', 'pipe', 'inherit'],
}
);
const stdout = Readable.toWeb(
childProcess.stdout.setEncoding('utf-8'));
// Result on Unix
assert.equal(
await readableStreamToString(stdout),
'Hello, how are you?\n'
);
// Result on Windows: 'Hello, how are you?\r\n'argsLassen Sie uns untersuchen, was passiert, wenn Metazeichen in args vorhanden sind
import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
async function echoUser({shell, args}) {
const childProcess = spawn(
`echo`, args,
{
stdio: ['ignore', 'pipe', 'inherit'],
shell,
}
);
const stdout = Readable.toWeb(
childProcess.stdout.setEncoding('utf-8'));
return readableStreamToString(stdout);
}
// Results on Unix
assert.equal(
await echoUser({shell: false, args: ['$USER']}), // (A)
'$USER\n'
);
assert.equal(
await echoUser({shell: true, args: ['$USER']}), // (B)
'rauschma\n'
);
assert.equal(
await echoUser({shell: true, args: [String.raw`\$USER`]}), // (C)
'$USER\n'
);$) keine Auswirkung (Zeile A).$USER als Variable interpretiert (Zeile B).Ähnliche Effekte treten bei anderen Metazeichen wie Sternchen (*) auf.
Dies waren zwei Beispiele für Unix-Shell-Metazeichen. Windows-Shells haben ihre eigenen Metazeichen und ihre eigenen Escaping-Methoden.
Verwenden wir mehr Shell-Features (was den Befehlsmodus erfordert)
import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
import {EOL} from 'node:os';
const childProcess = spawn(
`(echo cherry && echo apple && echo banana) | sort`,
{
stdio: ['ignore', 'pipe', 'inherit'],
shell: true,
}
);
const stdout = Readable.toWeb(
childProcess.stdout.setEncoding('utf-8'));
assert.equal(
await readableStreamToString(stdout),
'apple\nbanana\ncherry\n'
);Bisher haben wir nur die Standardausgabe eines Kindprozesses gelesen. Aber wir können auch Daten an die Standardeingabe senden
import {Readable, Writable} from 'node:stream';
import {spawn} from 'node:child_process';
const childProcess = spawn(
`sort`, // (A)
{
stdio: ['pipe', 'pipe', 'inherit'],
}
);
const stdin = Writable.toWeb(childProcess.stdin); // (B)
const writer = stdin.getWriter(); // (C)
try {
await writer.write('Cherry\n');
await writer.write('Apple\n');
await writer.write('Banana\n');
} finally {
writer.close();
}
const stdout = Readable.toWeb(
childProcess.stdout.setEncoding('utf-8'));
assert.equal(
await readableStreamToString(stdout),
'Apple\nBanana\nCherry\n'
);Wir verwenden den Shell-Befehl sort (Zeile A), um Zeilen zu sortieren.
In Zeile B verwenden wir Writable.toWeb(), um einen nativen Node.js-Stream in einen Web-Stream zu konvertieren (weitere Informationen finden Sie in §10 „Web-Streams unter Node.js verwenden“).
Wie man über einen Writer in einen WritableStream schreibt (Zeile C) wird ebenfalls im Kapitel über Web-Streams erklärt.
Wir ließen zuvor eine Shell den folgenden Befehl ausführen
(echo cherry && echo apple && echo banana) | sort
Im folgenden Beispiel machen wir das Piping manuell, von den Echos (Zeile A) zum Sortieren (Zeile B)
import {Readable, Writable} from 'node:stream';
import {spawn} from 'node:child_process';
const echo = spawn( // (A)
`echo cherry && echo apple && echo banana`,
{
stdio: ['ignore', 'pipe', 'inherit'],
shell: true,
}
);
const sort = spawn( // (B)
`sort`,
{
stdio: ['pipe', 'pipe', 'inherit'],
shell: true,
}
);
//==== Transferring chunks from echo.stdout to sort.stdin ====
const echoOut = Readable.toWeb(
echo.stdout.setEncoding('utf-8'));
const sortIn = Writable.toWeb(sort.stdin);
const sortInWriter = sortIn.getWriter();
try {
for await (const chunk of echoOut) { // (C)
await sortInWriter.write(chunk);
}
} finally {
sortInWriter.close();
}
//==== Reading sort.stdout ====
const sortOut = Readable.toWeb(
sort.stdout.setEncoding('utf-8'));
assert.equal(
await readableStreamToString(sortOut),
'apple\nbanana\ncherry\n'
);ReadableStreams wie echoOut sind asynchron iterierbar. Deshalb können wir eine for-await-of-Schleife verwenden, um ihre *Chunks* (die Fragmente der gestreamten Daten) zu lesen. Weitere Informationen finden Sie in §10 „Web-Streams unter Node.js verwenden“.
Es gibt drei Hauptarten von fehlgeschlagenen Exits
Der folgende Code demonstriert, was passiert, wenn ein Kindprozess nicht gestartet werden kann. In diesem Fall ist die Ursache, dass der Pfad der Shell nicht auf eine ausführbare Datei zeigt (Zeile A).
import {spawn} from 'node:child_process';
const childProcess = spawn(
'echo hello',
{
stdio: ['inherit', 'inherit', 'pipe'],
shell: '/bin/does-not-exist', // (A)
}
);
childProcess.on('error', (err) => { // (B)
assert.equal(
err.toString(),
'Error: spawn /bin/does-not-exist ENOENT'
);
});Dies ist das erste Mal, dass wir Ereignisse zur Arbeit mit Kindprozessen verwenden. In Zeile B registrieren wir einen Ereignis-Listener für das 'error'-Ereignis. Der Kindprozess startet nach Abschluss des aktuellen Codefragments. Das hilft, Race Conditions zu vermeiden: Wenn wir mit dem Zuhören beginnen, können wir sicher sein, dass das Ereignis noch nicht emittiert wurde.
Wenn der Shell-Code einen Fehler enthält, erhalten wir kein 'error'-Ereignis (Zeile B), sondern ein 'exit'-Ereignis mit einem Exit-Code ungleich null (Zeile A)
import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
const childProcess = spawn(
'does-not-exist',
{
stdio: ['inherit', 'inherit', 'pipe'],
shell: true,
}
);
childProcess.on('exit',
async (exitCode, signalCode) => { // (A)
assert.equal(exitCode, 127);
assert.equal(signalCode, null);
const stderr = Readable.toWeb(
childProcess.stderr.setEncoding('utf-8'));
assert.equal(
await readableStreamToString(stderr),
'/bin/sh: does-not-exist: command not found\n'
);
}
);
childProcess.on('error', (err) => { // (B)
console.error('We never get here!');
});Wenn ein Prozess unter Unix beendet wird, ist der Exit-Code null (Zeile C) und der Signal-Code ist ein String (Zeile D)
import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
const childProcess = spawn(
'kill $$', // (A)
{
stdio: ['inherit', 'inherit', 'pipe'],
shell: true,
}
);
console.log(childProcess.pid); // (B)
childProcess.on('exit', async (exitCode, signalCode) => {
assert.equal(exitCode, null); // (C)
assert.equal(signalCode, 'SIGTERM'); // (D)
const stderr = Readable.toWeb(
childProcess.stderr.setEncoding('utf-8'));
assert.equal(
await readableStreamToString(stderr),
'' // (E)
);
});Beachten Sie, dass keine Fehlerausgabe erfolgt (Zeile E).
Anstatt dass der Kindprozess sich selbst beendet (Zeile A), hätten wir ihn auch länger pausieren und über die Prozess-ID, die wir in Zeile B protokolliert haben, manuell beenden können.
Was passiert, wenn wir einen Kindprozess unter Windows beenden?
exitCode ist 1.signalCode ist null.Manchmal wollen wir nur warten, bis ein Befehl abgeschlossen ist. Dies kann über Ereignisse und über Promises erreicht werden.
import * as fs from 'node:fs';
import {spawn} from 'node:child_process';
const childProcess = spawn(
`(echo first && echo second) > tmp-file.txt`,
{
shell: true,
stdio: 'inherit',
}
);
childProcess.on('exit', (exitCode, signalCode) => { // (A)
assert.equal(exitCode, 0);
assert.equal(signalCode, null);
assert.equal(
fs.readFileSync('tmp-file.txt', {encoding: 'utf-8'}),
'first\nsecond\n'
);
});Wir verwenden das Standard-Node.js-Ereignismuster und registrieren einen Listener für das 'exit'-Ereignis (Zeile A).
import * as fs from 'node:fs';
import {spawn} from 'node:child_process';
const childProcess = spawn(
`(echo first && echo second) > tmp-file.txt`,
{
shell: true,
stdio: 'inherit',
}
);
const {exitCode, signalCode} = await onExit(childProcess); // (A)
assert.equal(exitCode, 0);
assert.equal(signalCode, null);
assert.equal(
fs.readFileSync('tmp-file.txt', {encoding: 'utf-8'}),
'first\nsecond\n'
);Die Hilfsfunktion onExit(), die wir in Zeile A verwenden, gibt ein Promise zurück, das erfüllt wird, wenn ein 'exit'-Ereignis emittiert wird
export function onExit(eventEmitter) {
return new Promise((resolve, reject) => {
eventEmitter.once('exit', (exitCode, signalCode) => {
if (exitCode === 0) { // (B)
resolve({exitCode, signalCode});
} else {
reject(new Error(
`Non-zero exit: code ${exitCode}, signal ${signalCode}`));
}
});
eventEmitter.once('error', (err) => { // (C)
reject(err);
});
});
}Wenn eventEmitter fehlschlägt, wird das zurückgegebene Promise abgelehnt und await löst in Zeile A eine Ausnahme aus. onExit() behandelt zwei Arten von Fehlern
exitCode ist nicht null (Zeile B). Das passiertexitCode größer als null.exitCode null und signalCode ist nicht null.'error'-Ereignis wird emittiert (Zeile C). Das passiert, wenn der Kindprozess nicht gestartet werden kann.In diesem Beispiel verwenden wir einen AbortController, um einen Shell-Befehl zu beenden
import {spawn} from 'node:child_process';
const abortController = new AbortController(); // (A)
const childProcess = spawn(
`echo Hello`,
{
stdio: 'inherit',
shell: true,
signal: abortController.signal, // (B)
}
);
childProcess.on('error', (err) => {
assert.equal(
err.toString(),
'AbortError: The operation was aborted'
);
});
abortController.abort(); // (C)Wir erstellen einen AbortController (Zeile A), übergeben sein Signal an spawn() (Zeile B) und beenden den Shell-Befehl über den AbortController (Zeile C).
Der Kindprozess startet asynchron (nachdem das aktuelle Codefragment ausgeführt wurde). Deshalb können wir abbrechen, bevor der Prozess überhaupt gestartet wurde, und deshalb sehen wir in diesem Fall keine Ausgabe.
.kill() beendenIm nächsten Beispiel beenden wir einen Kindprozess über die Methode .kill() (letzte Zeile)
import {spawn} from 'node:child_process';
const childProcess = spawn(
`echo Hello`,
{
stdio: 'inherit',
shell: true,
}
);
childProcess.on('exit', (exitCode, signalCode) => {
assert.equal(exitCode, null);
assert.equal(signalCode, 'SIGTERM');
});
childProcess.kill(); // default argument value: 'SIGTERM'Auch hier beenden wir den Kindprozess, bevor er gestartet wurde (asynchron!), und es gibt keine Ausgabe.
spawnSync()spawnSync(
command: string,
args?: Array<string>,
options?: Object
): ObjectspawnSync() ist die synchrone Version von spawn() – sie wartet, bis der Kindprozess beendet ist, bevor sie synchron(!) ein Objekt zurückgibt.
Die Parameter sind weitgehend dieselben wie die von spawn(). options hat einige zusätzliche Eigenschaften – z. B.
.input: string | TypedArray | DataView.encoding: string (Standard: 'buffer')Die Funktion gibt ein Objekt zurück. Seine interessantesten Eigenschaften sind
.stdout: Buffer | string.stderr: Buffer | string.status: number | nullnull. Entweder der Exit-Code oder der Signal-Code sind nicht null..signal: string | nullnull. Entweder der Exit-Code oder der Signal-Code sind nicht null..error?: ErrorBei dem asynchronen spawn() lief der Kindprozess parallel und wir konnten Standard-I/O über Streams lesen. Im Gegensatz dazu sammelt das synchrone spawnSync() die Inhalte der Streams und gibt sie uns synchron zurück (siehe nächste Unterabschnitt).
Bei Verwendung des synchronen spawnSync() wird der Kindprozess für den Befehl synchron gestartet. Der folgende Code demonstriert dies
import {spawnSync} from 'node:child_process';
spawnSync(
'echo', ['Command starts'],
{
stdio: 'inherit',
shell: true,
}
);
console.log('After spawnSync()');Dies ist die Ausgabe
Command starts
After spawnSync()
Der folgende Code demonstriert, wie Standardausgabe gelesen wird
import {spawnSync} from 'node:child_process';
const result = spawnSync(
`echo rock && echo paper && echo scissors`,
{
stdio: ['ignore', 'pipe', 'inherit'], // (A)
encoding: 'utf-8', // (B)
shell: true,
}
);
console.log(result);
assert.equal(
result.stdout, // (C)
'rock\npaper\nscissors\n'
);
assert.equal(result.stderr, null); // (D)In Zeile A verwenden wir options.stdio, um spawnSync() mitzuteilen, dass wir nur an der Standardausgabe interessiert sind. Wir ignorieren die Standardeingabe und leiten die Standardfehlerausgabe an den Elternprozess weiter.
Infolgedessen erhalten wir nur eine Ergebnis-Eigenschaft für die Standardausgabe (Zeile C) und die Eigenschaft für die Standardfehlerausgabe ist null (Zeile D).
Da wir nicht auf die Streams zugreifen können, die spawnSync() intern zur Verarbeitung der Standard-I/O des Kindprozesses verwendet, teilen wir ihm mit, welche Kodierung es verwenden soll, über options.encoding (Zeile B).
Wir können Daten über die Options-Eigenschaft .input (Zeile A) an den Standardeingabe-Stream eines Kindprozesses senden
import {spawnSync} from 'node:child_process';
const result = spawnSync(
`sort`,
{
stdio: ['pipe', 'pipe', 'inherit'],
encoding: 'utf-8',
input: 'Cherry\nApple\nBanana\n', // (A)
}
);
assert.equal(
result.stdout,
'Apple\nBanana\nCherry\n'
);Es gibt drei Hauptarten von fehlgeschlagenen Exits (wenn der Exit-Code nicht null ist)
Wenn das Starten fehlschlägt, emittiert spawn() ein 'error'-Ereignis. Im Gegensatz dazu setzt spawnSync() result.error auf ein Fehlerobjekt
import {spawnSync} from 'node:child_process';
const result = spawnSync(
'echo hello',
{
stdio: ['ignore', 'inherit', 'pipe'],
encoding: 'utf-8',
shell: '/bin/does-not-exist',
}
);
assert.equal(
result.error.toString(),
'Error: spawnSync /bin/does-not-exist ENOENT'
);Wenn ein Fehler in der Shell auftritt, ist der Exit-Code result.status größer als null und result.signal ist null
import {spawnSync} from 'node:child_process';
const result = spawnSync(
'does-not-exist',
{
stdio: ['ignore', 'inherit', 'pipe'],
encoding: 'utf-8',
shell: true,
}
);
assert.equal(result.status, 127);
assert.equal(result.signal, null);
assert.equal(
result.stderr, '/bin/sh: does-not-exist: command not found\n'
);Wenn der Kindprozess unter Unix beendet wird, enthält result.signal den Namen des Signals und result.status ist null
import {spawnSync} from 'node:child_process';
const result = spawnSync(
'kill $$',
{
stdio: ['ignore', 'inherit', 'pipe'],
encoding: 'utf-8',
shell: true,
}
);
assert.equal(result.status, null);
assert.equal(result.signal, 'SIGTERM');
assert.equal(result.stderr, ''); // (A)Beachten Sie, dass keine Ausgabe an den Standardfehlerausgabe-Stream gesendet wurde (Zeile A).
Wenn wir einen Kindprozess unter Windows beenden
result.status ist 1result.signal ist nullresult.stderr ist ''spawn()In diesem Abschnitt betrachten wir zwei asynchrone Funktionen im Modul node:child_process, die auf spawn() basieren
exec()execFile()Wir ignorieren fork() in diesem Kapitel. Zitat der Node.js-Dokumentation
fork()startet einen neuen Node.js-Prozess und ruft ein angegebenes Modul mit einem eingerichteten IPC-Kommunikationskanal auf, der das Senden von Nachrichten zwischen Eltern- und Kindprozess ermöglicht.
exec()exec(
command: string,
options?: Object,
callback?: (error, stdout, stderr) => void
): ChildProcessexec() führt einen Befehl in einem neu gestarteten Shell aus. Die Hauptunterschiede zu spawn() sind
exec() auch ein Ergebnis über einen Callback: Entweder ein Fehlerobjekt oder den Inhalt von stdout und stderr.spawn() nur 'error'-Ereignisse, wenn der Kindprozess nicht gestartet werden kann. Die anderen beiden Fehler werden über Exit-Codes und (unter Unix) Signal-Codes behandelt.args.options.shell ist true.import {exec} from 'node:child_process';
const childProcess = exec(
'echo Hello',
(error, stdout, stderr) => {
if (error) {
console.error('error: ' + error.toString());
return;
}
console.log('stdout: ' + stdout); // 'stdout: Hello\n'
console.error('stderr: ' + stderr); // 'stderr: '
}
);exec() kann über util.promisify() in eine Promise-basierte Funktion umgewandelt werden
{stdout, stderr}error des Callbacks, aber mit zwei zusätzlichen Eigenschaften: .stdout und .stderr.import * as util from 'node:util';
import * as child_process from 'node:child_process';
const execAsync = util.promisify(child_process.exec);
try {
const resultPromise = execAsync('echo Hello');
const {childProcess} = resultPromise;
const obj = await resultPromise;
console.log(obj); // { stdout: 'Hello\n', stderr: '' }
} catch (err) {
console.error(err);
}execFile()execFile(file, args?, options?, callback?): ChildProcess
Funktioniert ähnlich wie exec(), mit folgenden Unterschieden
args wird unterstützt.options.shell ist false.Wie exec() kann auch execFile() über util.promisify() in eine Promise-basierte Funktion umgewandelt werden.
spawnAsync()execSync()execSync(
command: string,
options?: Object
): Buffer | stringexecSync() führt einen Befehl in einem neuen Kindprozess aus und wartet synchron, bis dieser Prozess beendet ist. Die Hauptunterschiede zu spawnSync() sind
spawnSync() nur dann eine .error-Eigenschaft, wenn der Kindprozess nicht gestartet werden kann. Die anderen beiden Fehler werden über Exit-Codes und (unter Unix) Signal-Codes behandelt.args.options.shell ist true.import {execSync} from 'node:child_process';
try {
const stdout = execSync('echo Hello');
console.log('stdout: ' + stdout); // 'stdout: Hello\n'
} catch (err) {
console.error('Error: ' + err.toString());
}execFileSync()execFileSync(file, args?, options?): Buffer | string
Funktioniert ähnlich wie execSync(), mit folgenden Unterschieden
args wird unterstützt.options.shell ist false.tinysh von Anton Medvedev ist eine kleine Bibliothek, die beim Starten von Shell-Befehlen hilft – z. B.
import sh from 'tinysh';
console.log(sh.ls('-l'));
console.log(sh.cat('README.md'));Wir können die Standardoptionen überschreiben, indem wir .call() verwenden, um ein Objekt als this zu übergeben
sh.tee.call({input: 'Hello, world!'}, 'file.txt');Wir können beliebige Eigenschaftsnamen verwenden, und tinysh führt den Shell-Befehl mit diesem Namen aus. Dies erreicht es über einen Proxy. Dies ist eine leicht modifizierte Version der tatsächlichen Bibliothek
import {execFileSync} from 'node:child_process';
const sh = new Proxy({}, {
get: (_, bin) => function (...args) { // (A)
return execFileSync(bin, args,
{
encoding: 'utf-8',
shell: true,
...this // (B)
}
);
},
});In Zeile A sehen wir, dass, wenn wir von sh eine Eigenschaft erhalten, deren Name bin ist, eine Funktion zurückgegeben wird, die execFileSync() aufruft und bin als erstes Argument verwendet.
Durch das Aufteilen von this in Zeile B können wir Optionen über .call() angeben. Die Standardwerte kommen zuerst, sodass sie über this überschrieben werden können.
Die Verwendung von der Bibliothek node-powershell unter Windows sieht wie folgt aus
import { PowerShell } from 'node-powershell';
PowerShell.$`echo "hello from PowerShell"`;'node:child_process'Allgemeine Einschränkungen
spawn() ist in diesem Fall einfacher, da es keinen Callback hat, der Fehler und Standard-I/O-Inhalte liefert.exec() und execFile()spawnSync(), execSync(), execFileSync()Asynchrone Funktionen – Wahl zwischen spawn() und exec() oder execFile()
exec() und execFile() haben zwei Vorteilespawn() wählen, wenn diese Vorteile für Sie keine Rolle spielen. Seine Signatur ist ohne den (optionalen) Callback einfacher.Synchrone Funktionen – Wahl zwischen spawnSync() und execSync() oder execFileSync()
execSync() und execFileSync() haben zwei BesonderheitenspawnSync(), wenn Sie mehr Informationen benötigen, als execSync() und execFileSync() über ihre Rückgabewerte und Exceptions liefern.Wahl zwischen exec() und execFile() (die gleichen Argumente gelten für die Wahl zwischen execSync() und execFileSync())
options.shell ist true in exec(), aber false in execFile().execFile() unterstützt args, exec() nicht.