Shell-Skripting mit Node.js
Sie können die Offline-Version dieses Buches (HTML, PDF, EPUB, MOBI) kaufen und damit die kostenlose Online-Version unterstützen.
(Werbung, bitte nicht blockieren.)

12 Shell-Befehle in Kindprozessen ausführen



In diesem Kapitel werden wir untersuchen, wie wir Shell-Befehle aus Node.js über das Modul 'node:child_process' ausführen können.

12.1 Überblick über dieses Kapitel

Das Modul 'node:child_process' hat eine Funktion zur Ausführung von Shell-Befehlen (in *gestarteten* Kindprozessen), die in zwei Versionen vorliegt

Wir werden zuerst spawn() und dann spawnSync() untersuchen. Abschließend betrachten wir die folgenden Funktionen, die auf ihnen basieren und relativ ähnlich sind

12.1.1 Windows vs. Unix

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).

12.1.2 Funktionalität, die wir in den Beispielen oft verwenden

Die folgende Funktionalität taucht oft in den Beispielen auf. Deshalb wird sie hier einmal erklärt

12.2 Prozesse asynchron starten: spawn()

12.2.1 Wie spawn() funktioniert

spawn(
  command: string,
  args?: Array<string>,
  options?: Object
): ChildProcess

spawn() 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.

12.2.1.1 Parameter: command

command ist ein String mit dem Shell-Befehl. Es gibt zwei Modi für die Verwendung dieses Parameters

Beide Modi werden später in diesem Kapitel demonstriert.

12.2.1.2 Parameter: options

Die folgenden options sind am interessantesten

12.2.1.3 options.stdio

Jeder 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

Anstatt options.stdio über ein Array anzugeben, können wir auch abkürzen

12.2.1.4 Ergebnis: Instanz von ChildProcess

spawn() gibt Instanzen von ChildProcess zurück.

Interessante Dateneigenschaften

Interessante Methoden

Interessante Ereignisse

Wir werden später sehen, wie Ereignisse in Promises umgewandelt werden können, die awaitet werden können.

12.2.2 Wann wird der Shell-Befehl ausgeführt?

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

12.2.3 Befehlsmodus vs. Argumentmodus

In diesem Abschnitt geben wir dieselbe Befehlsaufrufung auf zwei Arten an

12.2.3.1 Befehlsmodus
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

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).

12.2.3.2 Argumentmodus
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'
12.2.3.3 Metazeichen in args

Lassen 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'
);

Ä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.

12.2.3.4 Ein komplizierterer Shell-Befehl

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

12.2.4 Daten an die Standardeingabe des Kindprozesses senden

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.

12.2.5 Manuelles Piping

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“.

12.2.6 Fehlgeschlagene Exits (einschließlich Fehler) behandeln

Es gibt drei Hauptarten von fehlgeschlagenen Exits

12.2.6.1 Der Kindprozess kann nicht gestartet werden

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.

12.2.6.2 Ein Fehler tritt in der Shell auf

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!');
});
12.2.6.3 Ein Prozess wird beendet

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?

12.2.7 Auf den Exit eines Kindprozesses warten

Manchmal wollen wir nur warten, bis ein Befehl abgeschlossen ist. Dies kann über Ereignisse und über Promises erreicht werden.

12.2.7.1 Warten über Ereignisse
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).

12.2.7.2 Warten über Promises
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

12.2.8 Kindprozesse beenden

12.2.8.1 Einen Kindprozess über einen AbortController beenden

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.

12.2.8.2 Einen Kindprozess über .kill() beenden

Im 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.

12.3 Prozesse synchron starten: spawnSync()

spawnSync(
  command: string,
  args?: Array<string>,
  options?: Object
): Object

spawnSync() 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.

Die Funktion gibt ein Objekt zurück. Seine interessantesten Eigenschaften sind

Bei 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).

12.3.1 Wann wird der Shell-Befehl ausgeführt?

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

12.3.2 Von stdout lesen

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).

12.3.3 Daten an die Standardeingabe des Kindprozesses senden

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

12.3.4 Fehlgeschlagene Exits (einschließlich Fehler) behandeln

Es gibt drei Hauptarten von fehlgeschlagenen Exits (wenn der Exit-Code nicht null ist)

12.3.4.1 Der Kindprozess kann nicht gestartet werden

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'
);
12.3.4.2 Ein Fehler tritt in der Shell auf

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'
);
12.3.4.3 Ein Prozess wird beendet

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

12.4 Asynchrone Hilfsfunktionen basierend auf spawn()

In diesem Abschnitt betrachten wir zwei asynchrone Funktionen im Modul node:child_process, die auf spawn() basieren

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.

12.4.1 exec()

exec(
  command: string,
  options?: Object,
  callback?: (error, stdout, stderr) => void
): ChildProcess

exec() führt einen Befehl in einem neu gestarteten Shell aus. Die Hauptunterschiede zu spawn() sind

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

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

12.4.2 execFile()

execFile(file, args?, options?, callback?): ChildProcess

Funktioniert ähnlich wie exec(), mit folgenden Unterschieden

Wie exec() kann auch execFile() über util.promisify() in eine Promise-basierte Funktion umgewandelt werden.

12.5 Synchrone Hilfsfunktionen basierend auf spawnAsync()

12.5.1 execSync()

execSync(
  command: string,
  options?: Object
): Buffer | string

execSync() führt einen Befehl in einem neuen Kindprozess aus und wartet synchron, bis dieser Prozess beendet ist. Die Hauptunterschiede zu spawnSync() sind

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

12.5.2 execFileSync()

execFileSync(file, args?, options?): Buffer | string

Funktioniert ähnlich wie execSync(), mit folgenden Unterschieden

12.6 Nützliche Bibliotheken

12.6.1 tinysh: ein Helfer zum Starten von Shell-Befehlen

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.

12.6.2 node-powershell: Ausführen von Windows PowerShell-Befehlen über Node.js

Die Verwendung von der Bibliothek node-powershell unter Windows sieht wie folgt aus

import { PowerShell } from 'node-powershell';
PowerShell.$`echo "hello from PowerShell"`;

12.7 Wahl zwischen den Funktionen des Moduls 'node:child_process'

Allgemeine Einschränkungen

Asynchrone Funktionen – Wahl zwischen spawn() und exec() oder execFile()

Synchrone Funktionen – Wahl zwischen spawnSync() und execSync() oder execFileSync()

Wahl zwischen exec() und execFile() (die gleichen Argumente gelten für die Wahl zwischen execSync() und execFileSync())