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:
import { $ } from "bun";
const response = await fetch("https://example.com");
// Usa Response como stdin.
await $`cat < ${response} | wc -c`; // 1256Recursos
- Multiplataforma: funciona no Windows, Linux e macOS. Em vez de
rimrafoucross-env, você pode usar Bun Shell sem instalar dependências extras. Comandos de shell comuns comols,cd,rmsã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 $:
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():
import { $ } from "bun";
await $`echo "Hello World!"`.quiet(); // Sem saídaE se você quiser acessar a saída do comando como texto? Use .text():
import { $ } from "bun";
// .text() automaticamente chama .quiet() para você
const welcome = await $`echo "Hello World!"`.text();
console.log(welcome); // Hello World!\nPor padrão, usar await retornará stdout e stderr como Buffers.
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.
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.
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.
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çadaRedirecionamento
A entrada ou saída de um comando pode ser redirecionada usando os operadores típicos do Bash:
<redireciona stdin>ou1>redireciona stdout2>redireciona stderr&>redireciona tanto stdout quanto stderr>>ou1>>redireciona stdout, anexando ao destino, em vez de sobrescrever2>>redireciona stderr, anexando ao destino, em vez de sobrescrever&>>redireciona tanto stdout quanto stderr, anexando ao destino, em vez de sobrescrever1>&2redireciona stdout para stderr (todas as escritas em stdout irão para stderr)2>&1redireciona 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 >:
import { $ } from "bun";
const buffer = Buffer.alloc(100);
await $`echo "Hello World!" > ${buffer}`;
console.log(buffer.toString()); // Hello World!\nOs 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 <:
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 bodyOs 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
import { $ } from "bun";
await $`cat < myfile.txt`;Exemplo: Redirecionar stdout -> arquivo
import { $ } from "bun";
await $`echo bun! > greeting.txt`;Exemplo: Redirecionar stderr -> arquivo
import { $ } from "bun";
await $`bun run index.ts 2> errors.txt`;Exemplo: Redirecionar stderr -> stdout
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
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:
import { $ } from "bun";
const result = await $`echo "Hello World!" | wc -w`.text();
console.log(result); // 2\nVocê também pode encanear com objetos 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\nSubstituição de comando ($(...))
Substituição de comando permite que você substitua a saída de outro script no script atual:
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:
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á:
import { $ } from "bun";
await $`echo \`echo hi\``;Em vez de imprimir:
hiO acima imprimirá:
echo hiRecomendamos em vez disso usar a sintaxe $(...).
Variáveis de ambiente
Variáveis de ambiente podem ser definidas como no bash:
import { $ } from "bun";
await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\nVocê pode usar interpolação de string para definir variáveis de ambiente:
import { $ } from "bun";
const foo = "bar123";
await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\nA entrada é escapada por padrão, prevenindo ataques de injeção de shell:
import { $ } from "bun";
const foo = "bar123; rm -rf /tmp";
await $`FOO=${foo} bun -e 'console.log(process.env.FOO)'`; // bar123; rm -rf /tmp\nAlterando 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():
import { $ } from "bun";
await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // barVocê pode alterar as variáveis de ambiente padrão para todos os comandos chamando $.env:
import { $ } from "bun";
$.env({ FOO: "bar" });
// o $FOO definido globalmente
await $`echo $FOO`; // bar
// o $FOO definido localmente
await $`echo $FOO`.env({ FOO: "baz" }); // bazVocê pode redefinir as variáveis de ambiente para o padrão chamando $.env() sem argumentos:
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():
import { $ } from "bun";
await $`pwd`.cwd("/tmp"); // /tmpVocê pode alterar o diretório de trabalho padrão para todos os comandos chamando $.cwd:
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():
import { $ } from "bun";
const result = await $`echo "Hello World!"`.text();
console.log(result); // Hello World!\nLendo saída como JSON
Para ler a saída de um comando como JSON, use .json():
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():
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:
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():
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 trabalhols: lista arquivos em um diretóriorm: remove arquivos e diretóriosecho: imprime textopwd: imprime o diretório de trabalhobun: executa bun no buncattouchmkdirwhichmvexittruefalseyesseqdirnamebasename
Parcialmente implementado:
mv: move arquivos e diretórios (faltando suporte cross-device)
Ainda não implementado, mas planejado:
- Veja Issue #9716 para a lista completa.
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:
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:
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' }:
import { $ } from "bun";
await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: command not found: foo
// => bun: command not found: bar
// => bazCarregador 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.
echo "Hello World! pwd=$(pwd)"bun ./script.shHello World! pwd=/home/demoScripts com Bun Shell são multiplataforma, o que significa que funcionam no Windows:
bun .\script.shHello World! pwd=C:\Users\DemoNotas 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:
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.
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.
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.