Skip to content

Bun Shell torna a criação de scripts de shell com JavaScript e TypeScript divertida. É um shell bash-like multiplataforma com interoperabilidade perfeita com JavaScript.

Início rápido:

ts
import { $ } from "bun";

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

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

Recursos

  • Multiplataforma: funciona no Windows, Linux e macOS. Em vez de rimraf ou cross-env, você pode usar Bun Shell sem instalar dependências extras. Comandos de shell comuns como ls, cd, rm são implementados nativamente.
  • Familiar: Bun Shell é um shell estilo bash, suportando redirecionamento, pipes, variáveis de ambiente e mais.
  • Globs: Padrões glob são suportados nativamente, incluindo **, *, {expansion} e mais.
  • Template literals: Template literals são usados para executar comandos de shell. Isso permite fácil interpolação de variáveis e expressões.
  • Segurança: Bun Shell escapa todas as strings por padrão, prevenindo ataques de injeção de shell.
  • Interoperabilidade JavaScript: Use Response, ArrayBuffer, Blob, Bun.file(path) e outros objetos JavaScript como stdin, stdout e stderr.
  • Scripting de shell: Bun Shell pode ser usado para executar scripts de shell (arquivos .bun.sh).
  • Interpretador customizado: Bun Shell é escrito em Zig, junto com seu lexer, parser e interpretador. Bun Shell é uma pequena linguagem de programação.

Primeiros passos

O comando de shell mais simples é echo. Para executá-lo, use a tag de template literal $:

js
import { $ } from "bun";

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

Por padrão, comandos de shell imprimem para stdout. Para silenciar a saída, chame .quiet():

js
import { $ } from "bun";

await $`echo "Hello World!"`.quiet(); // Sem saída

E se você quiser acessar a saída do comando como texto? Use .text():

js
import { $ } from "bun";

// .text() automaticamente chama .quiet() para você
const welcome = await $`echo "Hello World!"`.text();

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

Por padrão, usar await retornará stdout e stderr como Buffers.

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

Tratamento de erros

Por padrão, códigos de saída diferentes de zero lançarão um erro. Este ShellError contém informações sobre o comando executado.

js
import { $ } from "bun";

try {
  const output = await $`algo-que-pode-falhar`.text();
  console.log(output);
} catch (err) {
  console.log(`Falhou com código ${err.exitCode}`);
  console.log(err.stdout.toString());
  console.log(err.stderr.toString());
}

O lançamento pode ser desativado com .nothrow(). O exitCode do resultado precisará ser verificado manualmente.

js
import { $ } from "bun";

const { stdout, stderr, exitCode } = await $`algo-que-pode-falhar`.nothrow().quiet();

if (exitCode !== 0) {
  console.log(`Código de saída diferente de zero ${exitCode}`);
}

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

O tratamento padrão de códigos de saída diferentes de zero pode ser configurado chamando .nothrow() ou .throws(boolean) na função $ em si.

js
import { $ } from "bun";
// shell promises não lançarão, significando que você terá que
// verificar `exitCode` manualmente em cada comando de shell.
$.nothrow(); // equivalente a $.throws(false)

// comportamento padrão, códigos de saída diferentes de zero lançarão um erro
$.throws(true);

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

await $`algo-que-pode-falhar`; // Nenhuma exceção lançada

Redirecionamento

A entrada ou saída de um comando pode ser redirecionada usando os operadores típicos do Bash:

  • < redireciona stdin
  • > ou 1> redireciona stdout
  • 2> redireciona stderr
  • &> redireciona tanto stdout quanto stderr
  • >> ou 1>> redireciona stdout, anexando ao destino, em vez de sobrescrever
  • 2>> redireciona stderr, anexando ao destino, em vez de sobrescrever
  • &>> redireciona tanto stdout quanto stderr, anexando ao destino, em vez de sobrescrever
  • 1>&2 redireciona stdout para stderr (todas as escritas em stdout irão para stderr)
  • 2>&1 redireciona stderr para stdout (todas as escritas em stderr irão para stdout)

Bun Shell também suporta redirecionamento de e para objetos JavaScript.

Exemplo: Redirecionar saída para objetos JavaScript (>)

Para redirecionar stdout para um objeto JavaScript, use o operador >:

js
import { $ } from "bun";

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

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

Os seguintes objetos JavaScript são suportados para redirecionamento para:

  • Buffer, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, ArrayBuffer, SharedArrayBuffer (escreve no buffer subjacente)
  • Bun.file(path), Bun.file(fd) (escreve no arquivo)

Exemplo: Redirecionar entrada de objetos JavaScript (<)

Para redirecionar a saída de objetos JavaScript para stdin, use o operador <:

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

Os seguintes objetos JavaScript são suportados para redirecionamento de:

  • Buffer, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, ArrayBuffer, SharedArrayBuffer (lê do buffer subjacente)
  • Bun.file(path), Bun.file(fd) (lê do arquivo)
  • Response (lê do body)

Exemplo: Redirecionar stdin -> arquivo

js
import { $ } from "bun";

await $`cat < myfile.txt`;

Exemplo: Redirecionar stdout -> arquivo

js
import { $ } from "bun";

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

Exemplo: Redirecionar stderr -> arquivo

js
import { $ } from "bun";

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

Exemplo: Redirecionar stderr -> stdout

js
import { $ } from "bun";

// redireciona stderr para stdout, então toda saída
// estará disponível em stdout
await $`bun run ./index.ts 2>&1`;

Exemplo: Redirecionar stdout -> stderr

js
import { $ } from "bun";

// redireciona stdout para stderr, então toda saída
// estará disponível em stderr
await $`bun run ./index.ts 1>&2`;

Piping (|)

Como no bash, você pode encanear a saída de um comando para outro:

js
import { $ } from "bun";

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

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

Você também pode encanear com objetos 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

Substituição de comando ($(...))

Substituição de comando permite que você substitua a saída de outro script no script atual:

js
import { $ } from "bun";

// Imprime o hash do commit atual
await $`echo Hash do commit atual: $(git rev-parse HEAD)`;

Esta é uma inserção textual da saída do comando e pode ser usada para, por exemplo, declarar uma variável de shell:

js
import { $ } from "bun";

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

NOTE

Como o Bun usa internamente a propriedade especial raw no template literal de entrada, usar a sintaxe de crase para substituição de comando não funcionará:

ts
import { $ } from "bun";

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

Em vez de imprimir:

hi

O acima imprimirá:

echo hi

Recomendamos em vez disso usar a sintaxe $(...).


Variáveis de ambiente

Variáveis de ambiente podem ser definidas como no bash:

js
import { $ } from "bun";

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

Você pode usar interpolação de string para definir variáveis de ambiente:

js
import { $ } from "bun";

const foo = "bar123";

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

A entrada é escapada por padrão, prevenindo ataques de injeção de 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

Alterando as variáveis de ambiente

Por padrão, process.env é usado como as variáveis de ambiente para todos os comandos.

Você pode alterar as variáveis de ambiente para um único comando chamando .env():

js
import { $ } from "bun";

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

Você pode alterar as variáveis de ambiente padrão para todos os comandos chamando $.env:

js
import { $ } from "bun";

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

// o $FOO definido globalmente
await $`echo $FOO`; // bar

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

Você pode redefinir as variáveis de ambiente para o padrão chamando $.env() sem argumentos:

js
import { $ } from "bun";

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

// o $FOO definido globalmente
await $`echo $FOO`; // bar

// o $FOO definido localmente
await $`echo $FOO`.env(undefined); // ""

Alterando o diretório de trabalho

Você pode alterar o diretório de trabalho de um comando passando uma string para .cwd():

js
import { $ } from "bun";

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

Você pode alterar o diretório de trabalho padrão para todos os comandos chamando $.cwd:

js
import { $ } from "bun";

$.cwd("/tmp");

// o diretório de trabalho definido globalmente
await $`pwd`; // /tmp

// o diretório de trabalho definido localmente
await $`pwd`.cwd("/"); // /

Lendo saída

Para ler a saída de um comando como string, use .text():

js
import { $ } from "bun";

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

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

Lendo saída como JSON

Para ler a saída de um comando como JSON, use .json():

js
import { $ } from "bun";

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

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

Lendo saída linha por linha

Para ler a saída de um comando linha por linha, use .lines():

js
import { $ } from "bun";

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

Você também pode usar .lines() em um comando completado:

js
import { $ } from "bun";

const search = "bun";

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

Lendo saída como Blob

Para ler a saída de um comando como Blob, use .blob():

js
import { $ } from "bun";

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

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

Comandos Builtin

Para compatibilidade multiplataforma, Bun Shell implementa um conjunto de comandos builtin, além de ler comandos da variável de ambiente PATH.

  • cd: altera o diretório de trabalho
  • ls: lista arquivos em um diretório
  • rm: remove arquivos e diretórios
  • echo: imprime texto
  • pwd: imprime o diretório de trabalho
  • bun: executa bun no bun
  • cat
  • touch
  • mkdir
  • which
  • mv
  • exit
  • true
  • false
  • yes
  • seq
  • dirname
  • basename

Parcialmente implementado:

  • mv: move arquivos e diretórios (faltando suporte cross-device)

Ainda não implementado, mas planejado:


Utilitários

Bun Shell também implementa um conjunto de utilitários para trabalhar com shells.

$.braces (expansão de chaves)

Esta função implementa expansão de chaves simples para comandos de shell:

js
import { $ } from "bun";

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

$.escape (escapar strings)

Expõe a lógica de escape do Bun Shell como uma função:

js
import { $ } from "bun";

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

Se você não quiser que sua string seja escapada, envolva-a em um objeto { raw: 'str' }:

js
import { $ } from "bun";

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

Carregador de arquivos .sh

Para scripts de shell simples, em vez de /bin/sh, você pode usar Bun Shell para executar scripts de shell.

Para fazer isso, basta executar o script com bun em um arquivo com a extensão .sh.

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

Scripts com Bun Shell são multiplataforma, o que significa que funcionam no Windows:

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

Notas de implementação

Bun Shell é uma pequena linguagem de programação no Bun que é implementada em Zig. Inclui um lexer, parser e interpretador escritos à mão. Ao contrário de bash, zsh e outros shells, Bun Shell executa operações concorrentemente.


Segurança no shell do Bun

Por design, o shell do Bun não invoca um shell do sistema (como /bin/sh) e é em vez disso uma reimplementação do bash que executa no mesmo processo do Bun, projetado com segurança em mente.

Ao analisar argumentos de comando, ele trata todas as variáveis interpoladas como strings únicas e literais.

Isso protege o shell do Bun contra injeção de comando:

js
import { $ } from "bun";

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

// SEGURO: `userInput` é tratado como uma string única entre aspas
await $`ls ${userInput}`;

No exemplo acima, userInput é tratado como uma string única. Isso faz com que o comando ls tente ler o conteúdo de um único diretório chamado "my-file; rm -rf /".

Considerações de segurança

Embora a injeção de comando seja prevenida por padrão, os desenvolvedores ainda são responsáveis pela segurança em certos cenários.

Similar às APIs Bun.spawn ou node:child_process.exec(), você pode intencionalmente executar um comando que gera um novo shell (e.g. bash -c) com argumentos.

Quando você faz isso, entrega o controle, e as proteções builtin do Bun não se aplicam mais à string interpretada por aquele novo shell.

js
import { $ } from "bun";

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

// INSEGURO: Você explicitamente iniciou um novo processo de shell com `bash -c`.
// Este novo shell executará o comando `touch`. Qualquer entrada de usuário
// passada desta forma deve ser rigorosamente sanitizada.
await $`bash -c "echo ${userInput}"`;

Injeção de argumento

O shell do Bun não pode saber como um comando externo interpreta seus próprios argumentos de linha de comando. Um atacante pode fornecer entrada que o programa alvo reconhece como uma de suas próprias opções ou flags, levando a comportamento não intencional.

js
import { $ } from "bun";

// Entrada maliciosa formatada como um flag de linha de comando do Git
const branch = "--upload-pack=echo pwned";

// INSEGURO: Embora o Bun passe a string com segurança como um único argumento,
// o próprio programa `git` vê e age sobre o flag malicioso.
await $`git ls-remote origin ${branch}`;

NOTE

**Recomendação** — Como é a melhor prática em toda linguagem, sempre sanitize entrada fornecida pelo usuário antes de passá-la como um argumento para um comando externo. A responsabilidade por validar argumentos cabe ao código da sua aplicação.

Créditos

Grandes partes desta API foram inspiradas por zx, dax e bnx. Obrigado aos autores desses projetos.

Bun by www.bunjs.com.cn edit