Skip to content

Bun Shell macht Shell-Skripting mit JavaScript und TypeScript Spaß. Es ist eine plattformübergreifende bash-ähnliche Shell mit nahtloser JavaScript-Interop.

Schnellstart:

ts
import { $ } from "bun";

const response = await fetch("https://example.com");

// Verwenden Sie Response als stdin.
await $`cat < ${response} | wc -c`; // 1256

Funktionen

  • Plattformübergreifend: Funktioniert unter Windows, Linux und macOS. Anstatt rimraf oder cross-env zu verwenden, können Sie Bun Shell ohne Installation zusätzlicher Abhängigkeiten verwenden. Häufige Shell-Befehle wie ls, cd, rm sind nativ implementiert.
  • Vertraut: Bun Shell ist eine bash-ähnliche Shell, die Umleitung, Pipes, Umgebungsvariablen und mehr unterstützt.
  • Globs: Glob-Muster werden nativ unterstützt, einschließlich **, *, {expansion} und mehr.
  • Template-Literale: Template-Literale werden zum Ausführen von Shell-Befehlen verwendet. Dies ermöglicht eine einfache Interpolation von Variablen und Ausdrücken.
  • Sicherheit: Bun Shell maskiert standardmäßig alle Strings und verhindert Shell-Injection-Angriffe.
  • JavaScript-Interop: Verwenden Sie Response, ArrayBuffer, Blob, Bun.file(path) und andere JavaScript-Objekte als stdin, stdout und stderr.
  • Shell-Skripting: Bun Shell kann zum Ausführen von Shell-Skripten (.bun.sh-Dateien) verwendet werden.
  • Benutzerdefinierter Interpreter: Bun Shell ist in Zig geschrieben, zusammen mit seinem Lexer, Parser und Interpreter. Bun Shell ist eine kleine Programmiersprache.

Erste Schritte

Der einfachste Shell-Befehl ist echo. Um ihn auszuführen, verwenden Sie den $ Template-Literal-Tag:

js
import { $ } from "bun";

await $`echo "Hello World!"`; // Hello World!

Standardmäßig geben Shell-Befehle stdout aus. Um die Ausgabe zu unterdrücken, rufen Sie .quiet() auf:

js
import { $ } from "bun";

await $`echo "Hello World!"`.quiet(); // Keine Ausgabe

Was ist, wenn Sie auf die Ausgabe des Befehls als Text zugreifen möchten? Verwenden Sie .text():

js
import { $ } from "bun";

// .text() ruft automatisch .quiet() für Sie auf
const welcome = await $`echo "Hello World!"`.text();

console.log(welcome); // Hello World!\n

Standardmäßig gibt await stdout und stderr als Buffer zurück.

js
import { $ } from "bun";

const { stdout, stderr } = await $`echo "Hello!"`.quiet();

console.log(stdout); // Buffer(7) [ 72, 101, 108, 108, 111, 33, 10 ]
console.log(stderr); // Buffer(0) []

Fehlerbehandlung

Standardmäßig lösen nicht-null Exit-Codes einen Fehler aus. Dieser ShellError enthält Informationen über den ausgeführten Befehl.

js
import { $ } from "bun";

try {
  const output = await $`something-that-may-fail`.text();
  console.log(output);
} catch (err) {
  console.log(`Fehlgeschlagen mit Code ${err.exitCode}`);
  console.log(err.stdout.toString());
  console.log(err.stderr.toString());
}

Das Auslösen kann mit .nothrow() deaktiviert werden. Der exitCode des Ergebnisses muss manuell überprüft werden.

js
import { $ } from "bun";

const { stdout, stderr, exitCode } = await $`something-that-may-fail`.nothrow().quiet();

if (exitCode !== 0) {
  console.log(`Nicht-null Exit-Code ${exitCode}`);
}

console.log(stdout);
console.log(stderr);

Die Standardbehandlung von nicht-null Exit-Codes kann durch Aufrufen von .nothrow() oder .throws(boolean) auf der $-Funktion selbst konfiguriert werden.

js
import { $ } from "bun";
// Shell-Promises lösen keine Fehler aus, was bedeutet, dass Sie
// `exitCode` bei jedem Shell-Befehl manuell überprüfen müssen.
$.nothrow(); // entspricht $.throws(false)

// Standardverhalten, nicht-null Exit-Codes lösen einen Fehler aus
$.throws(true);

// Alias für $.nothrow()
$.throws(false);

await $`something-that-may-fail`; // Keine Ausnahme ausgelöst

Umleitung

Die Eingabe oder Ausgabe eines Befehls kann mit den typischen Bash-Operatoren umgeleitet werden:

  • < stdin umleiten
  • > oder 1> stdout umleiten
  • 2> stderr umleiten
  • &> sowohl stdout als auch stderr umleiten
  • >> oder 1>> stdout umleiten, an das Ziel anhängen, anstatt zu überschreiben
  • 2>> stderr umleiten, an das Ziel anhängen, anstatt zu überschreiben
  • &>> sowohl stdout als auch stderr umleiten, an das Ziel anhängen, anstatt zu überschreiben
  • 1>&2 stdout nach stderr umleiten (alle Schreibvorgänge nach stdout werden stattdessen in stderr ausgeführt)
  • 2>&1 stderr nach stdout umleiten (alle Schreibvorgänge nach stderr werden stattdessen in stdout ausgeführt)

Bun Shell unterstützt auch die Umleitung von und zu JavaScript-Objekten.

Beispiel: Ausgabe an JavaScript-Objekte umleiten (>)

Um stdout an ein JavaScript-Objekt umzuleiten, verwenden Sie den >-Operator:

js
import { $ } from "bun";

const buffer = Buffer.alloc(100);
await $`echo "Hello World!" > ${buffer}`;

console.log(buffer.toString()); // Hello World!\n

Die folgenden JavaScript-Objekte werden für die Umleitung unterstützt:

  • Buffer, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, ArrayBuffer, SharedArrayBuffer (schreibt in den zugrunde liegenden Puffer)
  • Bun.file(path), Bun.file(fd) (schreibt in die Datei)

Beispiel: Eingabe von JavaScript-Objekten umleiten (<)

Um die Ausgabe von JavaScript-Objekten an stdin umzuleiten, verwenden Sie den <-Operator:

js
import { $ } from "bun";

const response = new Response("hello i am a response body");

const result = await $`cat < ${response}`.text();

console.log(result); // hello i am a response body

Die folgenden JavaScript-Objekte werden für die Umleitung unterstützt:

  • Buffer, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, ArrayBuffer, SharedArrayBuffer (liest aus dem zugrunde liegenden Puffer)
  • Bun.file(path), Bun.file(fd) (liest aus der Datei)
  • Response (liest aus dem Body)

Beispiel: stdin -> Datei umleiten

js
import { $ } from "bun";

await $`cat < myfile.txt`;

Beispiel: stdout -> Datei umleiten

js
import { $ } from "bun";

await $`echo bun! > greeting.txt`;

Beispiel: stderr -> Datei umleiten

js
import { $ } from "bun";

await $`bun run index.ts 2> errors.txt`;

Beispiel: stderr -> stdout umleiten

js
import { $ } from "bun";

// leitet stderr nach stdout um, sodass alle Ausgaben
// auf stdout verfügbar sind
await $`bun run ./index.ts 2>&1`;

Beispiel: stdout -> stderr umleiten

js
import { $ } from "bun";

// leitet stdout nach stderr um, sodass alle Ausgaben
// auf stderr verfügbar sind
await $`bun run ./index.ts 1>&2`;

Pipes (|)

Wie in bash können Sie die Ausgabe eines Befehls an einen anderen weiterleiten:

js
import { $ } from "bun";

const result = await $`echo "Hello World!" | wc -w`.text();

console.log(result); // 2\n

Sie können auch mit JavaScript-Objekten pipen:

js
import { $ } from "bun";

const response = new Response("hello i am a response body");

const result = await $`cat < ${response} | wc -w`.text();

console.log(result); // 6\n

Befehlssubstitution ($(...))

Befehlssubstitution ermöglicht es Ihnen, die Ausgabe eines anderen Skripts in das aktuelle Skript einzufügen:

js
import { $ } from "bun";

// Gibt den Hash des aktuellen Commits aus
await $`echo Hash of current commit: $(git rev-parse HEAD)`;

Dies ist eine textuelle Einfügung der Ausgabe des Befehls und kann verwendet werden, um beispielsweise eine Shell-Variable zu deklarieren:

js
import { $ } from "bun";

await $`
  REV=$(git rev-parse HEAD)
  docker built -t myapp:$REV
  echo Done building docker image "myapp:$REV"
`;

NOTE

Da Bun intern die spezielle raw Eigenschaft des Eingabe-Template-Literals verwendet, funktioniert die Verwendung der Backtick-Syntax für Befehlssubstitution nicht:

ts
import { $ } from "bun";

await $`echo \`echo hi\``;

Anstatt auszugeben:

hi

Gibt das Obige aus:

echo hi

Wir empfehlen stattdessen, bei der $(...)-Syntax zu bleiben.


Umgebungsvariablen

Umgebungsvariablen können wie in bash gesetzt werden:

js
import { $ } from "bun";

await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\n

Sie können String-Interpolation verwenden, um Umgebungsvariablen zu setzen:

js
import { $ } from "bun";

const foo = "bar123";

await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\n

Die Eingabe wird standardmäßig maskiert, um Shell-Injection-Angriffe zu verhindern:

js
import { $ } from "bun";

const foo = "bar123; rm -rf /tmp";

await $`FOO=${foo} bun -e 'console.log(process.env.FOO)'`; // bar123; rm -rf /tmp\n

Ändern der Umgebungsvariablen

Standardmäßig wird process.env als Umgebungsvariablen für alle Befehle verwendet.

Sie können die Umgebungsvariablen für einen einzelnen Befehl ändern, indem Sie .env() aufrufen:

js
import { $ } from "bun";

await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // bar

Sie können die Standard-Umgebungsvariablen für alle Befehle ändern, indem Sie $.env aufrufen:

js
import { $ } from "bun";

$.env({ FOO: "bar" });

// das global gesetzte $FOO
await $`echo $FOO`; // bar

// das lokal gesetzte $FOO
await $`echo $FOO`.env({ FOO: "baz" }); // baz

Sie können die Umgebungsvariablen auf den Standard zurücksetzen, indem Sie $.env() ohne Argumente aufrufen:

js
import { $ } from "bun";

$.env({ FOO: "bar" });

// das global gesetzte $FOO
await $`echo $FOO`; // bar

// das lokal gesetzte $FOO
await $`echo $FOO`.env(undefined); // ""

Ändern des Arbeitsverzeichnisses

Sie können das Arbeitsverzeichnis eines Befehls ändern, indem Sie einen String an .cwd() übergeben:

js
import { $ } from "bun";

await $`pwd`.cwd("/tmp"); // /tmp

Sie können das Standard-Arbeitsverzeichnis für alle Befehle ändern, indem Sie $.cwd aufrufen:

js
import { $ } from "bun";

$.cwd("/tmp");

// das global gesetzte Arbeitsverzeichnis
await $`pwd`; // /tmp

// das lokal gesetzte Arbeitsverzeichnis
await $`pwd`.cwd("/"); // /

Ausgabe lesen

Um die Ausgabe eines Befehls als String zu lesen, verwenden Sie .text():

js
import { $ } from "bun";

const result = await $`echo "Hello World!"`.text();

console.log(result); // Hello World!\n

Ausgabe als JSON lesen

Um die Ausgabe eines Befehls als JSON zu lesen, verwenden Sie .json():

js
import { $ } from "bun";

const result = await $`echo '{"foo": "bar"}'`.json();

console.log(result); // { foo: "bar" }

Ausgabe Zeile für Zeile lesen

Um die Ausgabe eines Befehls Zeile für Zeile zu lesen, verwenden Sie .lines():

js
import { $ } from "bun";

for await (let line of $`echo "Hello World!"`.lines()) {
  console.log(line); // Hello World!
}

Sie können .lines() auch bei einem abgeschlossenen Befehl verwenden:

js
import { $ } from "bun";

const search = "bun";

for await (let line of $`cat list.txt | grep ${search}`.lines()) {
  console.log(line);
}

Ausgabe als Blob lesen

Um die Ausgabe eines Befehls als Blob zu lesen, verwenden Sie .blob():

js
import { $ } from "bun";

const result = await $`echo "Hello World!"`.blob();

console.log(result); // Blob(13) { size: 13, type: "text/plain" }

Eingebaute Befehle

Für plattformübergreifende Kompatibilität implementiert Bun Shell eine Reihe eingebauter Befehle, zusätzlich zum Lesen von Befehlen aus der PATH-Umgebungsvariablen.

  • cd: Arbeitsverzeichnis ändern
  • ls: Dateien in einem Verzeichnis auflisten
  • rm: Dateien und Verzeichnisse entfernen
  • echo: Text ausgeben
  • pwd: Arbeitsverzeichnis ausgeben
  • bun: bun in bun ausführen
  • cat
  • touch
  • mkdir
  • which
  • mv
  • exit
  • true
  • false
  • yes
  • seq
  • dirname
  • basename

Teilweise implementiert:

  • mv: Dateien und Verzeichnisse verschieben (fehlende geräteübergreifende Unterstützung)

Noch nicht implementiert, aber geplant:


Dienstprogramme

Bun Shell implementiert auch eine Reihe von Dienstprogrammen für die Arbeit mit Shells.

$.braces (Klammererweiterung)

Diese Funktion implementiert eine einfache Klammererweiterung für Shell-Befehle:

js
import { $ } from "bun";

await $.braces(`echo {1,2,3}`);
// => ["echo 1", "echo 2", "echo 3"]

$.escape (Strings maskieren)

Macht Buns Shell-Maskierungslogik als Funktion verfügbar:

js
import { $ } from "bun";

console.log($.escape('$(foo) `bar` "baz"'));
// => \$(foo) \`bar\` \"baz\"

Wenn Sie nicht möchten, dass Ihr String maskiert wird, wickeln Sie ihn in ein { raw: 'str' }-Objekt:

js
import { $ } from "bun";

await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: command not found: foo
// => bun: command not found: bar
// => baz

.sh Datei-Loader

Für einfache Shell-Skripte können Sie anstelle von /bin/sh Bun Shell zum Ausführen von Shell-Skripten verwenden.

Dazu führen Sie das Skript einfach mit bun auf einer Datei mit der .sh-Erweiterung aus.

sh
echo "Hello World! pwd=$(pwd)"
sh
bun ./script.sh
txt
Hello World! pwd=/home/demo

Skripte mit Bun Shell sind plattformübergreifend, was bedeutet, dass sie unter Windows funktionieren:

powershell
bun .\script.sh
txt
Hello World! pwd=C:\Users\Demo

Implementierungshinweise

Bun Shell ist eine kleine Programmiersprache in Bun, die in Zig implementiert ist. Sie enthält einen handgeschriebenen Lexer, Parser und Interpreter. Im Gegensatz zu bash, zsh und anderen Shells führt Bun Shell Operationen gleichzeitig aus.


Sicherheit in der Bun-Shell

By Design ruft die Bun-Shell keine System-Shell (wie /bin/sh) auf und ist stattdessen eine Neuimplementierung von bash, die im selben Bun-Prozess läuft und mit Blick auf Sicherheit entwickelt wurde.

Beim Parsen von Befehlsargumenten behandelt sie alle interpolierten Variablen als einzelne, wörtliche Strings.

Dies schützt die Bun-Shell vor Befehlsinjektion:

js
import { $ } from "bun";

const userInput = "my-file.txt; rm -rf /";

// SICHER: `userInput` wird als einzelner maskierter String behandelt
await $`ls ${userInput}`;

Im obigen Beispiel wird userInput als einzelner String behandelt. Dies bewirkt, dass der ls-Befehl versucht, den Inhalt eines einzelnen Verzeichnisses namens "my-file; rm -rf /" zu lesen.

Sicherheitshinweise

Obwohl Befehlsinjektion standardmäßig verhindert wird, sind Entwickler in bestimmten Szenarien weiterhin für die Sicherheit verantwortlich.

Ähnlich wie bei den Bun.spawn- oder node:child_process.exec()-APIs können Sie absichtlich einen Befehl ausführen, der eine neue Shell (z.B. bash -c) mit Argumenten startet.

Wenn Sie dies tun, geben Sie die Kontrolle ab, und Buns integrierte Schutzmaßnahmen gelten nicht mehr für den String, der von dieser neuen Shell interpretiert wird.

js
import { $ } from "bun";

const userInput = "world; touch /tmp/pwned";

// UNSICHER: Sie haben explizit einen neuen Shell-Prozess mit `bash -c` gestartet.
// Diese neue Shell führt den `touch`-Befehl aus. Alle auf diese Weise übergebenen
// Benutzereingaben müssen rigoros bereinigt werden.
await $`bash -c "echo ${userInput}"`;

Argumentinjektion

Die Bun-Shell kann nicht wissen, wie ein externer Befehl seine eigenen Befehlszeilenargumente interpretiert. Ein Angreifer kann Eingaben liefern, die das Zielprogramm als eine seiner eigenen Optionen oder Flags erkennt, was zu unbeabsichtigtem Verhalten führt.

js
import { $ } from "bun";

// Bösartige Eingabe formatiert als Git-Befehlszeilen-Flag
const branch = "--upload-pack=echo pwned";

// UNSICHER: Obwohl Bun den String sicher als einzelnes Argument übergibt,
// sieht und reagiert das `git`-Programm selbst auf das bösartige Flag.
await $`git ls-remote origin ${branch}`;

NOTE

**Empfehlung** — Wie es in jeder Sprache beste Praxis ist, bereinigen Sie immer benutzerbereitgestellte Eingaben, bevor Sie sie als Argument an einen externen Befehl übergeben. Die Verantwortung für die Validierung von Argumenten liegt bei Ihrem Anwendungscode.

Danksagungen

Große Teile dieser API wurden von zx, dax und bnx inspiriert. Vielen Dank an die Autoren dieser Projekte.

Bun von www.bunjs.com.cn bearbeitet