Bun Shell macht Shell-Skripting mit JavaScript und TypeScript Spaß. Es ist eine plattformübergreifende bash-ähnliche Shell mit nahtloser JavaScript-Interop.
Schnellstart:
import { $ } from "bun";
const response = await fetch("https://example.com");
// Verwenden Sie Response als stdin.
await $`cat < ${response} | wc -c`; // 1256Funktionen
- Plattformübergreifend: Funktioniert unter Windows, Linux und macOS. Anstatt
rimrafodercross-envzu verwenden, können Sie Bun Shell ohne Installation zusätzlicher Abhängigkeiten verwenden. Häufige Shell-Befehle wiels,cd,rmsind 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:
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:
import { $ } from "bun";
await $`echo "Hello World!"`.quiet(); // Keine AusgabeWas ist, wenn Sie auf die Ausgabe des Befehls als Text zugreifen möchten? Verwenden Sie .text():
import { $ } from "bun";
// .text() ruft automatisch .quiet() für Sie auf
const welcome = await $`echo "Hello World!"`.text();
console.log(welcome); // Hello World!\nStandardmäßig gibt await stdout und stderr als Buffer zurück.
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.
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.
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.
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östUmleitung
Die Eingabe oder Ausgabe eines Befehls kann mit den typischen Bash-Operatoren umgeleitet werden:
<stdin umleiten>oder1>stdout umleiten2>stderr umleiten&>sowohl stdout als auch stderr umleiten>>oder1>>stdout umleiten, an das Ziel anhängen, anstatt zu überschreiben2>>stderr umleiten, an das Ziel anhängen, anstatt zu überschreiben&>>sowohl stdout als auch stderr umleiten, an das Ziel anhängen, anstatt zu überschreiben1>&2stdout nach stderr umleiten (alle Schreibvorgänge nach stdout werden stattdessen in stderr ausgeführt)2>&1stderr 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:
import { $ } from "bun";
const buffer = Buffer.alloc(100);
await $`echo "Hello World!" > ${buffer}`;
console.log(buffer.toString()); // Hello World!\nDie 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:
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 bodyDie 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
import { $ } from "bun";
await $`cat < myfile.txt`;Beispiel: stdout -> Datei umleiten
import { $ } from "bun";
await $`echo bun! > greeting.txt`;Beispiel: stderr -> Datei umleiten
import { $ } from "bun";
await $`bun run index.ts 2> errors.txt`;Beispiel: stderr -> stdout umleiten
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
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:
import { $ } from "bun";
const result = await $`echo "Hello World!" | wc -w`.text();
console.log(result); // 2\nSie können auch mit JavaScript-Objekten pipen:
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\nBefehlssubstitution ($(...))
Befehlssubstitution ermöglicht es Ihnen, die Ausgabe eines anderen Skripts in das aktuelle Skript einzufügen:
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:
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:
import { $ } from "bun";
await $`echo \`echo hi\``;Anstatt auszugeben:
hiGibt das Obige aus:
echo hiWir empfehlen stattdessen, bei der $(...)-Syntax zu bleiben.
Umgebungsvariablen
Umgebungsvariablen können wie in bash gesetzt werden:
import { $ } from "bun";
await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\nSie können String-Interpolation verwenden, um Umgebungsvariablen zu setzen:
import { $ } from "bun";
const foo = "bar123";
await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\nDie Eingabe wird standardmäßig maskiert, um Shell-Injection-Angriffe zu verhindern:
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:
import { $ } from "bun";
await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // barSie können die Standard-Umgebungsvariablen für alle Befehle ändern, indem Sie $.env aufrufen:
import { $ } from "bun";
$.env({ FOO: "bar" });
// das global gesetzte $FOO
await $`echo $FOO`; // bar
// das lokal gesetzte $FOO
await $`echo $FOO`.env({ FOO: "baz" }); // bazSie können die Umgebungsvariablen auf den Standard zurücksetzen, indem Sie $.env() ohne Argumente aufrufen:
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:
import { $ } from "bun";
await $`pwd`.cwd("/tmp"); // /tmpSie können das Standard-Arbeitsverzeichnis für alle Befehle ändern, indem Sie $.cwd aufrufen:
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():
import { $ } from "bun";
const result = await $`echo "Hello World!"`.text();
console.log(result); // Hello World!\nAusgabe als JSON lesen
Um die Ausgabe eines Befehls als JSON zu lesen, verwenden Sie .json():
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():
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:
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():
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 ändernls: Dateien in einem Verzeichnis auflistenrm: Dateien und Verzeichnisse entfernenecho: Text ausgebenpwd: Arbeitsverzeichnis ausgebenbun: bun in bun ausführencattouchmkdirwhichmvexittruefalseyesseqdirnamebasename
Teilweise implementiert:
mv: Dateien und Verzeichnisse verschieben (fehlende geräteübergreifende Unterstützung)
Noch nicht implementiert, aber geplant:
- Siehe Issue #9716 für die vollständige Liste.
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:
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:
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:
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.
echo "Hello World! pwd=$(pwd)"bun ./script.shHello World! pwd=/home/demoSkripte mit Bun Shell sind plattformübergreifend, was bedeutet, dass sie unter Windows funktionieren:
bun .\script.shHello World! pwd=C:\Users\DemoImplementierungshinweise
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:
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.
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.
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.