Skip to content

Bun Shell rend le script shell avec JavaScript et TypeScript amusant. C'est un shell de type bash multiplateforme avec une interopérabilité transparente avec JavaScript.

Démarrage rapide :

ts
import { $ } from "bun";

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

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

Fonctionnalités

  • Multiplateforme : fonctionne sur Windows, Linux et macOS. Au lieu de rimraf ou cross-env, vous pouvez utiliser Bun Shell sans installer de dépendances supplémentaires. Les commandes shell courantes comme ls, cd, rm sont implémentées nativement.
  • Familier : Bun Shell est un shell de type bash, prenant en charge la redirection, les pipes, les variables d'environnement et plus encore.
  • Globs : Les motifs glob sont pris en charge nativement, y compris **, *, {expansion}, et plus encore.
  • Template literals : Les template literals sont utilisés pour exécuter des commandes shell. Cela permet une interpolation facile des variables et des expressions.
  • Sécurité : Bun Shell échappe toutes les chaînes par défaut, empêchant les attaques par injection shell.
  • Interopérabilité JavaScript : Utilisez Response, ArrayBuffer, Blob, Bun.file(path) et d'autres objets JavaScript comme stdin, stdout et stderr.
  • Script shell : Bun Shell peut être utilisé pour exécuter des scripts shell (fichiers .bun.sh).
  • Interpréteur personnalisé : Bun Shell est écrit en Zig, ainsi que son lexer, son parser et son interpréteur. Bun Shell est un petit langage de programmation.

Démarrage

La commande shell la plus simple est echo. Pour l'exécuter, utilisez le tag de template literal $ :

js
import { $ } from "bun";

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

Par défaut, les commandes shell affichent sur stdout. Pour réduire le bruit de sortie, appelez .quiet() :

js
import { $ } from "bun";

await $`echo "Hello World!"`.quiet(); // Pas de sortie

Et si vous voulez accéder à la sortie de la commande sous forme de texte ? Utilisez .text() :

js
import { $ } from "bun";

// .text() appelle automatiquement .quiet() pour vous
const welcome = await $`echo "Hello World!"`.text();

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

Par défaut, await renvoie stdout et stderr sous forme de 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) []

Gestion des erreurs

Par défaut, les codes de sortie non nuls lanceront une erreur. Cette ShellError contient des informations sur la commande exécutée.

js
import { $ } from "bun";

try {
  const output = await $`something-that-may-fail`.text();
  console.log(output);
} catch (err) {
  console.log(`Échec avec le code ${err.exitCode}`);
  console.log(err.stdout.toString());
  console.log(err.stderr.toString());
}

Le lancement peut être désactivé avec .nothrow(). Le exitCode du résultat devra être vérifié manuellement.

js
import { $ } from "bun";

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

if (exitCode !== 0) {
  console.log(`Code de sortie non nul ${exitCode}`);
}

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

La gestion par défaut des codes de sortie non nuls peut être configurée en appelant .nothrow() ou .throws(boolean) sur la fonction $ elle-même.

js
import { $ } from "bun";
// les promesses shell ne lanceront pas, ce qui signifie que vous devrez
// vérifier `exitCode` manuellement sur chaque commande shell.
$.nothrow(); // équivalent à $.throws(false)

// comportement par défaut, les codes de sortie non nuls lanceront une erreur
$.throws(true);

// alias pour $.nothrow()
$.throws(false);

await $`something-that-may-fail`; // Aucune exception lancée

Redirection

L'entrée ou la sortie d'une commande peut être redirigée en utilisant les opérateurs Bash typiques :

  • < rediriger stdin
  • > ou 1> rediriger stdout
  • 2> rediriger stderr
  • &> rediriger à la fois stdout et stderr
  • >> ou 1>> rediriger stdout, en ajoutant à la destination, au lieu d'écraser
  • 2>> rediriger stderr, en ajoutant à la destination, au lieu d'écraser
  • &>> rediriger à la fois stdout et stderr, en ajoutant à la destination, au lieu d'écraser
  • 1>&2 rediriger stdout vers stderr (toutes les écritures sur stdout seront plutôt dans stderr)
  • 2>&1 rediriger stderr vers stdout (toutes les écritures sur stderr seront plutôt dans stdout)

Bun Shell prend également en charge la redirection depuis et vers des objets JavaScript.

Exemple : Rediriger la sortie vers des objets JavaScript (>)

Pour rediriger stdout vers un objet JavaScript, utilisez l'opérateur > :

js
import { $ } from "bun";

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

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

Les objets JavaScript suivants sont pris en charge pour la redirection vers :

  • Buffer, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, ArrayBuffer, SharedArrayBuffer (écrit dans le buffer sous-jacent)
  • Bun.file(path), Bun.file(fd) (écrit dans le fichier)

Exemple : Rediriger l'entrée depuis des objets JavaScript (<)

Pour rediriger la sortie d'objets JavaScript vers stdin, utilisez l'opérateur < :

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

Les objets JavaScript suivants sont pris en charge pour la redirection depuis :

  • Buffer, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, ArrayBuffer, SharedArrayBuffer (lit depuis le buffer sous-jacent)
  • Bun.file(path), Bun.file(fd) (lit depuis le fichier)
  • Response (lit depuis le body)

Exemple : Rediriger stdin -> fichier

js
import { $ } from "bun";

await $`cat < myfile.txt`;

Exemple : Rediriger stdout -> fichier

js
import { $ } from "bun";

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

Exemple : Rediriger stderr -> fichier

js
import { $ } from "bun";

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

Exemple : Rediriger stderr -> stdout

js
import { $ } from "bun";

// redirige stderr vers stdout, donc toute la sortie
// sera disponible sur stdout
await $`bun run ./index.ts 2>&1`;

Exemple : Rediriger stdout -> stderr

js
import { $ } from "bun";

// redirige stdout vers stderr, donc toute la sortie
// sera disponible sur stderr
await $`bun run ./index.ts 1>&2`;

Piping (|)

Comme dans bash, vous pouvez piper la sortie d'une commande vers une autre :

js
import { $ } from "bun";

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

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

Vous pouvez également piper avec des objets 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

Substitution de commande ($(...))

La substitution de commande vous permet de substituer la sortie d'un autre script dans le script actuel :

js
import { $ } from "bun";

// Affiche le hash du commit actuel
await $`echo Hash of current commit: $(git rev-parse HEAD)`;

Ceci est une insertion textuelle de la sortie de la commande et peut être utilisé, par exemple, pour déclarer une variable shell :

js
import { $ } from "bun";

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

NOTE

Parce que Bun utilise en interne la propriété spéciale raw sur le template literal d'entrée, l'utilisation de la syntaxe backtick pour la substitution de commande ne fonctionnera pas :

ts
import { $ } from "bun";

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

Au lieu d'afficher :

hi

Ce qui précède affichera :

echo hi

Nous recommandons plutôt de s'en tenir à la syntaxe $(...).


Variables d'environnement

Les variables d'environnement peuvent être définies comme dans bash :

js
import { $ } from "bun";

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

Vous pouvez utiliser l'interpolation de chaîne pour définir des variables d'environnement :

js
import { $ } from "bun";

const foo = "bar123";

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

L'entrée est échappée par défaut, empêchant les attaques par injection shell :

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

Modification des variables d'environnement

Par défaut, process.env est utilisé comme variables d'environnement pour toutes les commandes.

Vous pouvez modifier les variables d'environnement pour une seule commande en appelant .env() :

js
import { $ } from "bun";

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

Vous pouvez modifier les variables d'environnement par défaut pour toutes les commandes en appelant $.env :

js
import { $ } from "bun";

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

// le $FOO défini globalement
await $`echo $FOO`; // bar

// le $FOO défini localement
await $`echo $FOO`.env({ FOO: "baz" }); // baz

Vous pouvez réinitialiser les variables d'environnement aux valeurs par défaut en appelant $.env() sans arguments :

js
import { $ } from "bun";

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

// le $FOO défini globalement
await $`echo $FOO`; // bar

// le $FOO défini localement
await $`echo $FOO`.env(undefined); // ""

Modification du répertoire de travail

Vous pouvez modifier le répertoire de travail d'une commande en passant une chaîne à .cwd() :

js
import { $ } from "bun";

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

Vous pouvez modifier le répertoire de travail par défaut pour toutes les commandes en appelant $.cwd :

js
import { $ } from "bun";

$.cwd("/tmp");

// le répertoire de travail défini globalement
await $`pwd`; // /tmp

// le répertoire de travail défini localement
await $`pwd`.cwd("/"); // /

Lecture de la sortie

Pour lire la sortie d'une commande sous forme de chaîne, utilisez .text() :

js
import { $ } from "bun";

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

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

Lecture de la sortie sous forme de JSON

Pour lire la sortie d'une commande sous forme de JSON, utilisez .json() :

js
import { $ } from "bun";

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

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

Lecture de la sortie ligne par ligne

Pour lire la sortie d'une commande ligne par ligne, utilisez .lines() :

js
import { $ } from "bun";

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

Vous pouvez également utiliser .lines() sur une commande terminée :

js
import { $ } from "bun";

const search = "bun";

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

Lecture de la sortie sous forme de Blob

Pour lire la sortie d'une commande sous forme de Blob, utilisez .blob() :

js
import { $ } from "bun";

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

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

Commandes intégrées

Pour la compatibilité multiplateforme, Bun Shell implémente un ensemble de commandes intégrées, en plus de la lecture des commandes depuis la variable d'environnement PATH.

  • cd : changer le répertoire de travail
  • ls : lister les fichiers dans un répertoire
  • rm : supprimer des fichiers et répertoires
  • echo : afficher du texte
  • pwd : afficher le répertoire de travail
  • bun : exécuter bun dans bun
  • cat
  • touch
  • mkdir
  • which
  • mv
  • exit
  • true
  • false
  • yes
  • seq
  • dirname
  • basename

Partiellement implémenté :

  • mv : déplacer des fichiers et répertoires (prise en charge inter-périphériques manquante)

Pas encore implémenté, mais prévu :


Utilitaires

Bun Shell implémente également un ensemble d'utilitaires pour travailler avec les shells.

$.braces (expansion des accolades)

Cette fonction implémente une expansion des accolades simple pour les commandes shell :

js
import { $ } from "bun";

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

$.escape (échapper les chaînes)

Expose la logique d'échappement de Bun Shell sous forme de fonction :

js
import { $ } from "bun";

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

Si vous ne voulez pas que votre chaîne soit échappée, enveloppez-la dans un objet { raw: 'str' } :

js
import { $ } from "bun";

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

Chargeur de fichier .sh

Pour les scripts shell simples, au lieu de /bin/sh, vous pouvez utiliser Bun Shell pour exécuter des scripts shell.

Pour ce faire, exécutez simplement le script avec bun sur un fichier avec l'extension .sh.

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

Les scripts avec Bun Shell sont multiplateformes, ce qui signifie qu'ils fonctionnent sur Windows :

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

Notes d'implémentation

Bun Shell est un petit langage de programmation dans Bun qui est implémenté en Zig. Il comprend un lexer, un parser et un interpréteur écrits à la main. Contrairement à bash, zsh et autres shells, Bun Shell exécute les opérations de manière concurrente.


Sécurité dans le shell Bun

Par conception, le shell Bun n'invoque pas un shell système (comme /bin/sh) et est plutôt une ré-implémentation de bash qui s'exécute dans le même processus Bun, conçue avec la sécurité à l'esprit.

Lors de l'analyse des arguments de commande, il traite toutes les variables interpolées comme des chaînes uniques et littérales.

Cela protège le shell Bun contre les injections de commande :

js
import { $ } from "bun";

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

// SÛR : `userInput` est traité comme une chaîne unique entre guillemets
await $`ls ${userInput}`;

Dans l'exemple ci-dessus, userInput est traité comme une chaîne unique. Cela amène la commande ls à essayer de lire le contenu d'un seul répertoire nommé "my-file; rm -rf /".

Considérations de sécurité

Bien que l'injection de commande soit empêchée par défaut, les développeurs sont toujours responsables de la sécurité dans certains scénarios.

Semblable aux API Bun.spawn ou node:child_process.exec(), vous pouvez intentionnellement exécuter une commande qui engendre un nouveau shell (par exemple bash -c) avec des arguments.

Lorsque vous faites cela, vous transférez le contrôle, et les protections intégrées de Bun ne s'appliquent plus à la chaîne interprétée par ce nouveau shell.

js
import { $ } from "bun";

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

// NON SÛR : Vous avez explicitement démarré un nouveau processus shell avec `bash -c`.
// Ce nouveau shell exécutera la commande `touch`. Toute entrée utilisateur
// transmise de cette manière doit être rigoureusement assainie.
await $`bash -c "echo ${userInput}"`;

Injection d'arguments

Le shell Bun ne peut pas savoir comment une commande externe interprète ses propres arguments de ligne de commande. Un attaquant peut fournir une entrée que le programme cible reconnaît comme l'une de ses propres options ou drapeaux, conduisant à un comportement inattendu.

js
import { $ } from "bun";

// Entrée malveillante formatée comme un drapeau de ligne de commande Git
const branch = "--upload-pack=echo pwned";

// NON SÛR : Bien que Bun transmette en toute sécurité la chaîne comme un seul argument,
// le programme `git` lui-même voit et agit sur le drapeau malveillant.
await $`git ls-remote origin ${branch}`;

NOTE

**Recommandation** — Comme c'est la meilleure pratique dans tous les langages, assainissez toujours l'entrée fournie par l'utilisateur avant de la passer comme argument à une commande externe. La responsabilité de valider les arguments incombe à votre code d'application.

Crédits

De grandes parties de cette API ont été inspirées par zx, dax, et bnx. Merci aux auteurs de ces projets.

Bun édité par www.bunjs.com.cn