Skip to content

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:

ts
import { $ } from "bun";

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

// Usa Response come stdin.
await $`cat < ${response} | wc -c`; // 1256

Caratteristiche

  • Multipiattaforma: funziona su Windows, Linux e macOS. Invece di rimraf o cross-env, puoi usare Bun Shell senza installare dipendenze extra. I comandi shell comuni come ls, cd, rm sono 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 $:

js
import { $ } from "bun";

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

Per impostazione predefinita, i comandi shell stampano su stdout. Per silenziare l'output, chiama .quiet():

js
import { $ } from "bun";

await $`echo "Hello World!"`.quiet(); // Nessun output

E se volessi accedere all'output del comando come testo? Usa .text():

js
import { $ } from "bun";

// .text() chiama automaticamente .quiet() per te
const welcome = await $`echo "Hello World!"`.text();

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

Per impostazione predefinita, await restituirà stdout e stderr come Buffer.

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) []

Gestione degli errori

Per impostazione predefinita, i codici di uscita diversi da zero lanceranno un errore. Questo ShellError contiene informazioni sul comando eseguito.

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

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

js
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 lanciata

Reindirizzamento

L'input o l'output di un comando possono essere reindirizzati usando i tipici operatori Bash:

  • < reindirizza stdin
  • > o 1> reindirizza stdout
  • 2> reindirizza stderr
  • &> reindirizza sia stdout che stderr
  • >> o 1>> reindirizza stdout, aggiungendo alla destinazione, invece di sovrascrivere
  • 2>> reindirizza stderr, aggiungendo alla destinazione, invece di sovrascrivere
  • &>> reindirizza sia stdout che stderr, aggiungendo alla destinazione, invece di sovrascrivere
  • 1>&2 reindirizza stdout a stderr (tutte le scritture su stdout saranno invece su stderr)
  • 2>&1 reindirizza 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 >:

js
import { $ } from "bun";

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

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

I 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 <:

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

I 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

js
import { $ } from "bun";

await $`cat < myfile.txt`;

Esempio: Reindirizza stdout -> file

js
import { $ } from "bun";

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

Esempio: Reindirizza stderr -> file

js
import { $ } from "bun";

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

Esempio: Reindirizza stderr -> stdout

js
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

js
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:

js
import { $ } from "bun";

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

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

Puoi anche pipeare con oggetti JavaScript:

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

Sostituzione di comandi ($(...))

La sostituzione di comandi ti consente di sostituire l'output di un altro script nello script corrente:

js
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:

js
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à:

ts
import { $ } from "bun";

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

Invece di stampare:

hi

Quanto sopra stamperà:

echo hi

Raccomandiamo invece di attenersi alla sintassi $(...).


Variabili d'ambiente

Le variabili d'ambiente possono essere impostate come in bash:

js
import { $ } from "bun";

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

Puoi usare l'interpolazione di stringhe per impostare le variabili d'ambiente:

js
import { $ } from "bun";

const foo = "bar123";

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

L'input viene escaped per impostazione predefinita, prevenendo attacchi di shell injection:

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

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

js
import { $ } from "bun";

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

Puoi cambiare le variabili d'ambiente predefinite per tutti i comandi chiamando $.env:

js
import { $ } from "bun";

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

// la $FOO impostata globalmente
await $`echo $FOO`; // bar

// la $FOO impostata localmente
await $`echo $FOO`.env({ FOO: "baz" }); // baz

Puoi resettare le variabili d'ambiente al predefinito chiamando $.env() senza argomenti:

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

js
import { $ } from "bun";

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

Puoi cambiare la directory di lavoro predefinita per tutti i comandi chiamando $.cwd:

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

js
import { $ } from "bun";

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

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

Leggere l'output come JSON

Per leggere l'output di un comando come JSON, usa .json():

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

js
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:

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

js
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 lavoro
  • ls: elenca i file in una directory
  • rm: rimuove file e directory
  • echo: stampa testo
  • pwd: stampa la directory di lavoro
  • bun: esegui bun in bun
  • cat
  • touch
  • mkdir
  • which
  • mv
  • exit
  • true
  • false
  • yes
  • seq
  • dirname
  • basename

Parzialmente implementati:

  • mv: sposta file e directory (manca il supporto cross-device)

Non ancora implementati, ma pianificati:


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:

js
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:

js
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' }:

js
import { $ } from "bun";

await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: comando non trovato: foo
// => bun: comando non trovato: bar
// => baz

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

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

Gli script con Bun Shell sono multipiattaforma, il che significa che funzionano su Windows:

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

Note 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:

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

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

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

Bun a cura di www.bunjs.com.cn