Bun Shell rende divertente lo scripting shell con JavaScript e TypeScript. È una shell simile a bash multipiattaforma con interoperabilità JavaScript senza soluzione di continuità.
Avvio rapido:
import { $ } from "bun";
const response = await fetch("https://example.com");
// Usa Response come stdin.
await $`cat < ${response} | wc -c`; // 1256Caratteristiche
- Multipiattaforma: funziona su Windows, Linux e macOS. Invece di
rimrafocross-env, puoi usare Bun Shell senza installare dipendenze extra. I comandi shell comuni comels,cd,rmsono implementati nativamente. - Familiare: Bun Shell è una shell simile a bash, che supporta reindirizzamento, pipe, variabili d'ambiente e altro.
- Glob: I pattern Glob sono supportati nativamente, inclusi
**,*,{expansion}e altro. - Template literals: I template literals sono usati per eseguire comandi shell. Questo consente un'interpolazione facile di variabili ed espressioni.
- Sicurezza: Bun Shell esegue l'escape di tutte le stringhe per impostazione predefinita, prevenendo attacchi di shell injection.
- Interoperabilità JavaScript: Usa
Response,ArrayBuffer,Blob,Bun.file(path)e altri oggetti JavaScript come stdin, stdout e stderr. - Scripting shell: Bun Shell può essere usato per eseguire script shell (file
.bun.sh). - Interprete personalizzato: Bun Shell è scritto in Zig, insieme al suo lexer, parser e interprete. Bun Shell è un piccolo linguaggio di programmazione.
Per iniziare
Il comando shell più semplice è echo. Per eseguirlo, usa il tag template literal $:
import { $ } from "bun";
await $`echo "Hello World!"`; // Hello World!Per impostazione predefinita, i comandi shell stampano su stdout. Per silenziare l'output, chiama .quiet():
import { $ } from "bun";
await $`echo "Hello World!"`.quiet(); // Nessun outputE se volessi accedere all'output del comando come testo? Usa .text():
import { $ } from "bun";
// .text() chiama automaticamente .quiet() per te
const welcome = await $`echo "Hello World!"`.text();
console.log(welcome); // Hello World!\nPer impostazione predefinita, await restituirà stdout e stderr come Buffer.
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) []Gestione degli errori
Per impostazione predefinita, i codici di uscita diversi da zero lanceranno un errore. Questo ShellError contiene informazioni sul comando eseguito.
import { $ } from "bun";
try {
const output = await $`something-that-may-fail`.text();
console.log(output);
} catch (err) {
console.log(`Fallito con codice ${err.exitCode}`);
console.log(err.stdout.toString());
console.log(err.stderr.toString());
}Il lancio può essere disabilitato con .nothrow(). Il exitCode del risultato dovrà essere controllato manualmente.
import { $ } from "bun";
const { stdout, stderr, exitCode } = await $`something-that-may-fail`.nothrow().quiet();
if (exitCode !== 0) {
console.log(`Codice di uscita diverso da zero ${exitCode}`);
}
console.log(stdout);
console.log(stderr);La gestione predefinita dei codici di uscita diversi da zero può essere configurata chiamando .nothrow() o .throws(boolean) sulla funzione $ stessa.
import { $ } from "bun";
// le promise shell non lanceranno, il che significa che dovrai
// controllare `exitCode` manualmente su ogni comando shell.
$.nothrow(); // equivalente a $.throws(false)
// comportamento predefinito, i codici di uscita diversi da zero lanceranno un errore
$.throws(true);
// alias per $.nothrow()
$.throws(false);
await $`something-that-may-fail`; // Nessuna eccezione lanciataReindirizzamento
L'input o l'output di un comando possono essere reindirizzati usando i tipici operatori Bash:
<reindirizza stdin>o1>reindirizza stdout2>reindirizza stderr&>reindirizza sia stdout che stderr>>o1>>reindirizza stdout, aggiungendo alla destinazione, invece di sovrascrivere2>>reindirizza stderr, aggiungendo alla destinazione, invece di sovrascrivere&>>reindirizza sia stdout che stderr, aggiungendo alla destinazione, invece di sovrascrivere1>&2reindirizza stdout a stderr (tutte le scritture su stdout saranno invece su stderr)2>&1reindirizza stderr a stdout (tutte le scritture su stderr saranno invece su stdout)
Bun Shell supporta anche il reindirizzamento da e verso oggetti JavaScript.
Esempio: Reindirizza output a oggetti JavaScript (>)
Per reindirizzare stdout a un oggetto JavaScript, usa l'operatore >:
import { $ } from "bun";
const buffer = Buffer.alloc(100);
await $`echo "Hello World!" > ${buffer}`;
console.log(buffer.toString()); // Hello World!\nI seguenti oggetti JavaScript sono supportati per il reindirizzamento a:
Buffer,Uint8Array,Uint16Array,Uint32Array,Int8Array,Int16Array,Int32Array,Float32Array,Float64Array,ArrayBuffer,SharedArrayBuffer(scrive nel buffer sottostante)Bun.file(path),Bun.file(fd)(scrive nel file)
Esempio: Reindirizza input da oggetti JavaScript (<)
Per reindirizzare l'output da oggetti JavaScript a stdin, usa l'operatore <:
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 bodyI seguenti oggetti JavaScript sono supportati per il reindirizzamento da:
Buffer,Uint8Array,Uint16Array,Uint32Array,Int8Array,Int16Array,Int32Array,Float32Array,Float64Array,ArrayBuffer,SharedArrayBuffer(legge dal buffer sottostante)Bun.file(path),Bun.file(fd)(legge dal file)Response(legge dal body)
Esempio: Reindirizza stdin -> file
import { $ } from "bun";
await $`cat < myfile.txt`;Esempio: Reindirizza stdout -> file
import { $ } from "bun";
await $`echo bun! > greeting.txt`;Esempio: Reindirizza stderr -> file
import { $ } from "bun";
await $`bun run index.ts 2> errors.txt`;Esempio: Reindirizza stderr -> stdout
import { $ } from "bun";
// reindirizza stderr a stdout, quindi tutto l'output
// sarà disponibile su stdout
await $`bun run ./index.ts 2>&1`;Esempio: Reindirizza stdout -> stderr
import { $ } from "bun";
// reindirizza stdout a stderr, quindi tutto l'output
// sarà disponibile su stderr
await $`bun run ./index.ts 1>&2`;Piping (|)
Come in bash, puoi pipeare l'output di un comando a un altro:
import { $ } from "bun";
const result = await $`echo "Hello World!" | wc -w`.text();
console.log(result); // 2\nPuoi anche pipeare con oggetti JavaScript:
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\nSostituzione di comandi ($(...))
La sostituzione di comandi ti consente di sostituire l'output di un altro script nello script corrente:
import { $ } from "bun";
// Stampa l'hash del commit corrente
await $`echo Hash of current commit: $(git rev-parse HEAD)`;Questa è un'inserzione testuale dell'output del comando e può essere usata, ad esempio, per dichiarare una variabile shell:
import { $ } from "bun";
await $`
REV=$(git rev-parse HEAD)
docker built -t myapp:$REV
echo Done building docker image "myapp:$REV"
`;NOTE
Poiché Bun usa internamente la proprietà speciale raw sul template literal di input, usare la sintassi con backtick per la sostituzione di comandi non funzionerà:
import { $ } from "bun";
await $`echo \`echo hi\``;Invece di stampare:
hiQuanto sopra stamperà:
echo hiRaccomandiamo invece di attenersi alla sintassi $(...).
Variabili d'ambiente
Le variabili d'ambiente possono essere impostate come in bash:
import { $ } from "bun";
await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\nPuoi usare l'interpolazione di stringhe per impostare le variabili d'ambiente:
import { $ } from "bun";
const foo = "bar123";
await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\nL'input viene escaped per impostazione predefinita, prevenendo attacchi di shell injection:
import { $ } from "bun";
const foo = "bar123; rm -rf /tmp";
await $`FOO=${foo} bun -e 'console.log(process.env.FOO)'`; // bar123; rm -rf /tmp\nModificare le variabili d'ambiente
Per impostazione predefinita, process.env è usato come variabili d'ambiente per tutti i comandi.
Puoi cambiare le variabili d'ambiente per un singolo comando chiamando .env():
import { $ } from "bun";
await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // barPuoi cambiare le variabili d'ambiente predefinite per tutti i comandi chiamando $.env:
import { $ } from "bun";
$.env({ FOO: "bar" });
// la $FOO impostata globalmente
await $`echo $FOO`; // bar
// la $FOO impostata localmente
await $`echo $FOO`.env({ FOO: "baz" }); // bazPuoi resettare le variabili d'ambiente al predefinito chiamando $.env() senza argomenti:
import { $ } from "bun";
$.env({ FOO: "bar" });
// la $FOO impostata globalmente
await $`echo $FOO`; // bar
// la $FOO impostata localmente
await $`echo $FOO`.env(undefined); // ""Modificare la directory di lavoro
Puoi cambiare la directory di lavoro di un comando passando una stringa a .cwd():
import { $ } from "bun";
await $`pwd`.cwd("/tmp"); // /tmpPuoi cambiare la directory di lavoro predefinita per tutti i comandi chiamando $.cwd:
import { $ } from "bun";
$.cwd("/tmp");
// la directory di lavoro impostata globalmente
await $`pwd`; // /tmp
// la directory di lavoro impostata localmente
await $`pwd`.cwd("/"); // /Leggere l'output
Per leggere l'output di un comando come stringa, usa .text():
import { $ } from "bun";
const result = await $`echo "Hello World!"`.text();
console.log(result); // Hello World!\nLeggere l'output come JSON
Per leggere l'output di un comando come JSON, usa .json():
import { $ } from "bun";
const result = await $`echo '{"foo": "bar"}'`.json();
console.log(result); // { foo: "bar" }Leggere l'output riga per riga
Per leggere l'output di un comando riga per riga, usa .lines():
import { $ } from "bun";
for await (let line of $`echo "Hello World!"`.lines()) {
console.log(line); // Hello World!
}Puoi anche usare .lines() su un comando completato:
import { $ } from "bun";
const search = "bun";
for await (let line of $`cat list.txt | grep ${search}`.lines()) {
console.log(line);
}Leggere l'output come Blob
Per leggere l'output di un comando come Blob, usa .blob():
import { $ } from "bun";
const result = await $`echo "Hello World!"`.blob();
console.log(result); // Blob(13) { size: 13, type: "text/plain" }Comandi Builtin
Per la compatibilità multipiattaforma, Bun Shell implementa un insieme di comandi builtin, oltre a leggere i comandi dalla variabile d'ambiente PATH.
cd: cambia la directory di lavorols: elenca i file in una directoryrm: rimuove file e directoryecho: stampa testopwd: stampa la directory di lavorobun: esegui bun in buncattouchmkdirwhichmvexittruefalseyesseqdirnamebasename
Parzialmente implementati:
mv: sposta file e directory (manca il supporto cross-device)
Non ancora implementati, ma pianificati:
- Vedi Issue #9716 per l'elenco completo.
Utility
Bun Shell implementa anche un insieme di utility per lavorare con le shell.
$.braces (espansione delle parentesi)
Questa funzione implementa la semplice espansione delle parentesi per i comandi shell:
import { $ } from "bun";
await $.braces(`echo {1,2,3}`);
// => ["echo 1", "echo 2", "echo 3"]$.escape (escape delle stringhe)
Espone la logica di escaping di Bun Shell come funzione:
import { $ } from "bun";
console.log($.escape('$(foo) `bar` "baz"'));
// => \$(foo) \`bar\` \"baz\"Se non vuoi che la tua stringa venga escaped, avvolgila in un oggetto { raw: 'str' }:
import { $ } from "bun";
await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: comando non trovato: foo
// => bun: comando non trovato: bar
// => bazLoader di file .sh
Per script shell semplici, invece di /bin/sh, puoi usare Bun Shell per eseguire script shell.
Per farlo, basta eseguire lo script con bun su un file con estensione .sh.
echo "Hello World! pwd=$(pwd)"bun ./script.shHello World! pwd=/home/demoGli script con Bun Shell sono multipiattaforma, il che significa che funzionano su Windows:
bun .\script.shHello World! pwd=C:\Users\DemoNote sull'implementazione
Bun Shell è un piccolo linguaggio di programmazione in Bun che è implementato in Zig. Include un lexer, parser e interprete scritti a mano. A differenza di bash, zsh e altre shell, Bun Shell esegue le operazioni contemporaneamente.
Sicurezza nella shell di Bun
Per progettazione, la shell di Bun non invoca una shell di sistema (come /bin/sh) ed è invece una re-implementazione di bash che esegue nello stesso processo Bun, progettata con la sicurezza in mente.
Quando analizza gli argomenti dei comandi, tratta tutte le variabili interpolate come stringhe singole e letterali.
Questo protegge la shell di Bun contro l'iniezione di comandi:
import { $ } from "bun";
const userInput = "my-file.txt; rm -rf /";
// SICURO: `userInput` è trattato come una singola stringa quotata
await $`ls ${userInput}`;Nell'esempio sopra, userInput è trattato come una singola stringa. Questo fa sì che il comando ls provi a leggere il contenuto di una singola directory chiamata "my-file; rm -rf /".
Considerazioni sulla sicurezza
Sebbene l'iniezione di comandi sia prevenuta per impostazione predefinita, gli sviluppatori sono ancora responsabili della sicurezza in alcuni scenari.
Simile alle API Bun.spawn o node:child_process.exec(), puoi intenzionalmente eseguire un comando che genera una nuova shell (ad esempio bash -c) con argomenti.
Quando fai questo, cedi il controllo e le protezioni builtin di Bun non si applicano più alla stringa interpretata da quella nuova shell.
import { $ } from "bun";
const userInput = "world; touch /tmp/pwned";
// NON SICURO: Hai esplicitamente avviato un nuovo processo shell con `bash -c`.
// Questa nuova shell eseguirà il comando `touch`. Qualsiasi input utente
// passato in questo modo deve essere rigorosamente sanificato.
await $`bash -c "echo ${userInput}"`;Iniezione di argomenti
La shell di Bun non può sapere come un comando esterno interpreta i propri argomenti da riga di comando. Un attaccante può fornire input che il programma target riconosce come una delle sue opzioni o flag, portando a comportamenti indesiderati.
import { $ } from "bun";
// Input dannoso formattato come flag da riga di comando di Git
const branch = "--upload-pack=echo pwned";
// NON SICURO: Anche se Bun passa in sicurezza la stringa come singolo argomento,
// il programma `git` stesso vede e agisce sul flag dannoso.
await $`git ls-remote origin ${branch}`;NOTE
**Raccomandazione** — Come è buona pratica in ogni linguaggio, sanifica sempre l'input fornito dall'utente prima di passarlo come argomento a un comando esterno. La responsabilità di convalidare gli argomenti spetta al codice della tua applicazione.Crediti
Gran parte di questa API è stata ispirata da zx, dax e bnx. Grazie agli autori di quei progetti.