Bun Shell делает скрипты оболочки с JavaScript и TypeScript увлекательными. Это кроссплатформенная оболочка, подобная bash, с бесшовной интеграцией JavaScript.
Быстрый старт:
import { $ } from "bun";
const response = await fetch("https://example.com");
// Используйте Response как stdin.
await $`cat < ${response} | wc -c`; // 1256Возможности
- Кроссплатформенность: работает на Windows, Linux и macOS. Вместо
rimrafилиcross-envвы можете использовать Bun Shell без установки дополнительных зависимостей. Общие команды оболочки, такие какls,cd,rm, реализованы нативно. - Знакомая: Bun Shell — это оболочка, подобная bash, поддерживающая перенаправление, конвейеры, переменные окружения и многое другое.
- Глобы: Глоб-паттерны поддерживаются нативно, включая
**,*,{expansion}и другие. - Шаблонные литералы: Шаблонные литералы используются для выполнения команд оболочки. Это позволяет легко интерполировать переменные и выражения.
- Безопасность: Bun Shell экранирует все строки по умолчанию, предотвращая атаки внедрения команд оболочки.
- Интеграция с JavaScript: Используйте
Response,ArrayBuffer,Blob,Bun.file(path)и другие объекты JavaScript как stdin, stdout и stderr. - Скрипты оболочки: Bun Shell можно использовать для запуска скриптов оболочки (файлы
.bun.sh). - Пользовательский интерпретатор: Bun Shell написан на Zig вместе с его лексером, парсером и интерпретатором. Bun Shell — это небольшой язык программирования.
Начало работы
Простейшая команда оболочки — echo. Чтобы выполнить её, используйте шаблонный литерал $:
import { $ } from "bun";
await $`echo "Hello World!"`; // Hello World!По умолчанию команды оболочки выводят в stdout. Чтобы отключить вывод, вызовите .quiet():
import { $ } from "bun";
await $`echo "Hello World!"`.quiet(); // Нет выводаЧто если вы хотите получить доступ к выводу команды как к тексту? Используйте .text():
import { $ } from "bun";
// .text() автоматически вызывает .quiet() для вас
const welcome = await $`echo "Hello World!"`.text();
console.log(welcome); // Hello World!\nПо умолчанию await возвращает stdout и stderr как 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) []Обработка ошибок
По умолчанию коды выхода, отличные от нуля, выбрасывают ошибку. Эта ShellError содержит информацию о выполненной команде.
import { $ } from "bun";
try {
const output = await $`something-that-may-fail`.text();
console.log(output);
} catch (err) {
console.log(`Не удалось с кодом ${err.exitCode}`);
console.log(err.stdout.toString());
console.log(err.stderr.toString());
}Выбрасывание можно отключить с помощью .nothrow(). Результат exitCode нужно будет проверять вручную.
import { $ } from "bun";
const { stdout, stderr, exitCode } = await $`something-that-may-fail`.nothrow().quiet();
if (exitCode !== 0) {
console.log(`Код выхода, отличный от нуля: ${exitCode}`);
}
console.log(stdout);
console.log(stderr);Обработка по умолчанию кодов выхода, отличных от нуля, может быть настроена вызовом .nothrow() или .throws(boolean) на самой функции $.
import { $ } from "bun";
// промисы оболочки не будут выбрасывать ошибку, что означает, что вам придется
// проверять `exitCode` вручную для каждой команды оболочки.
$.nothrow(); // эквивалентно $.throws(false)
// поведение по умолчанию, коды выхода, отличные от нуля, будут выбрасывать ошибку
$.throws(true);
// псевдоним для $.nothrow()
$.throws(false);
await $`something-that-may-fail`; // Исключение не выбрасываетсяПеренаправление
Вход или выход команды может быть перенаправлен с использованием типичных операторов Bash:
<перенаправление stdin>или1>перенаправление stdout2>перенаправление stderr&>перенаправление и stdout, и stderr>>или1>>перенаправление stdout, добавление в назначение вместо перезаписи2>>перенаправление stderr, добавление в назначение вместо перезаписи&>>перенаправление и stdout, и stderr, добавление в назначение вместо перезаписи1>&2перенаправление stdout в stderr (все записи в stdout будут вместо этого в stderr)2>&1перенаправление stderr в stdout (все записи в stderr будут вместо этого в stdout)
Bun Shell также поддерживает перенаправление из и в объекты JavaScript.
Пример: Перенаправление вывода в объекты JavaScript (>)
Для перенаправления stdout в объект JavaScript используйте оператор >:
import { $ } from "bun";
const buffer = Buffer.alloc(100);
await $`echo "Hello World!" > ${buffer}`;
console.log(buffer.toString()); // Hello World!\nСледующие объекты JavaScript поддерживаются для перенаправления в:
Buffer,Uint8Array,Uint16Array,Uint32Array,Int8Array,Int16Array,Int32Array,Float32Array,Float64Array,ArrayBuffer,SharedArrayBuffer(запись в базовый буфер)Bun.file(path),Bun.file(fd)(запись в файл)
Пример: Перенаправление ввода из объектов JavaScript (<)
Для перенаправления вывода из объектов JavaScript в stdin используйте оператор <:
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Следующие объекты JavaScript поддерживаются для перенаправления из:
Buffer,Uint8Array,Uint16Array,Uint32Array,Int8Array,Int16Array,Int32Array,Float32Array,Float64Array,ArrayBuffer,SharedArrayBuffer(чтение из базового буфера)Bun.file(path),Bun.file(fd)(чтение из файла)Response(чтение из тела)
Пример: Перенаправление stdin -> файл
import { $ } from "bun";
await $`cat < myfile.txt`;Пример: Перенаправление stdout -> файл
import { $ } from "bun";
await $`echo bun! > greeting.txt`;Пример: Перенаправление stderr -> файл
import { $ } from "bun";
await $`bun run index.ts 2> errors.txt`;Пример: Перенаправление stderr -> stdout
import { $ } from "bun";
// перенаправляет stderr в stdout, поэтому весь вывод
// будет доступен в stdout
await $`bun run ./index.ts 2>&1`;Пример: Перенаправление stdout -> stderr
import { $ } from "bun";
// перенаправляет stdout в stderr, поэтому весь вывод
// будет доступен в stderr
await $`bun run ./index.ts 1>&2`;Конвейеры (|)
Как в bash, вы можете передавать вывод одной команды в другую:
import { $ } from "bun";
const result = await $`echo "Hello World!" | wc -w`.text();
console.log(result); // 2\nВы также можете использовать конвейеры с объектами 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\nПодстановка команд ($(...))
Подстановка команд позволяет подставить вывод другого скрипта в текущий скрипт:
import { $ } from "bun";
// Выводит хэш текущего коммита
await $`echo Хэш текущего коммита: $(git rev-parse HEAD)`;Это текстовая вставка вывода команды и может использоваться, например, для объявления переменной оболочки:
import { $ } from "bun";
await $`
REV=$(git rev-parse HEAD)
docker built -t myapp:$REV
echo Готово создание образа docker "myapp:$REV"
`;NOTE
Поскольку Bun внутренне использует специальное свойство raw на входном шаблонном литерале, использование синтаксиса обратных кавычек для подстановки команд не будет работать:
import { $ } from "bun";
await $`echo \`echo hi\``;Вместо вывода:
hiВыше будет выведено:
echo hiВместо этого мы рекомендуем придерживаться синтаксиса $(...).
Переменные окружения
Переменные окружения можно установить, как в bash:
import { $ } from "bun";
await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\nВы можете использовать интерполяцию строк для установки переменных окружения:
import { $ } from "bun";
const foo = "bar123";
await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\nВвод экранируется по умолчанию, предотвращая атаки внедрения команд оболочки:
import { $ } from "bun";
const foo = "bar123; rm -rf /tmp";
await $`FOO=${foo} bun -e 'console.log(process.env.FOO)'`; // bar123; rm -rf /tmp\nИзменение переменных окружения
По умолчанию process.env используется как переменные окружения для всех команд.
Вы можете изменить переменные окружения для одной команды, вызвав .env():
import { $ } from "bun";
await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // barВы можете изменить переменные окружения по умолчанию для всех команд, вызвав $.env:
import { $ } from "bun";
$.env({ FOO: "bar" });
// глобально установленная $FOO
await $`echo $FOO`; // bar
// локально установленная $FOO
await $`echo $FOO`.env({ FOO: "baz" }); // bazВы можете сбросить переменные окружения на значения по умолчанию, вызвав $.env() без аргументов:
import { $ } from "bun";
$.env({ FOO: "bar" });
// глобально установленная $FOO
await $`echo $FOO`; // bar
// локально установленная $FOO
await $`echo $FOO`.env(undefined); // ""Изменение рабочего каталога
Вы можете изменить рабочий каталог команды, передав строку в .cwd():
import { $ } from "bun";
await $`pwd`.cwd("/tmp"); // /tmpВы можете изменить рабочий каталог по умолчанию для всех команд, вызвав $.cwd:
import { $ } from "bun";
$.cwd("/tmp");
// глобально установленный рабочий каталог
await $`pwd`; // /tmp
// локально установленный рабочий каталог
await $`pwd`.cwd("/"); // /Чтение вывода
Для чтения вывода команды как строки используйте .text():
import { $ } from "bun";
const result = await $`echo "Hello World!"`.text();
console.log(result); // Hello World!\nЧтение вывода как JSON
Для чтения вывода команды как JSON используйте .json():
import { $ } from "bun";
const result = await $`echo '{"foo": "bar"}'`.json();
console.log(result); // { foo: "bar" }Чтение вывода построчно
Для чтения вывода команды построчно используйте .lines():
import { $ } from "bun";
for await (let line of $`echo "Hello World!"`.lines()) {
console.log(line); // Hello World!
}Вы также можете использовать .lines() на завершенной команде:
import { $ } from "bun";
const search = "bun";
for await (let line of $`cat list.txt | grep ${search}`.lines()) {
console.log(line);
}Чтение вывода как Blob
Для чтения вывода команды как Blob используйте .blob():
import { $ } from "bun";
const result = await $`echo "Hello World!"`.blob();
console.log(result); // Blob(13) { size: 13, type: "text/plain" }Встроенные команды
Для кроссплатформенной совместимости Bun Shell реализует набор встроенных команд в дополнение к чтению команд из переменной окружения PATH.
cd: изменить рабочий каталогls: список файлов в каталогеrm: удаление файлов и каталоговecho: вывод текстаpwd: вывод рабочего каталогаbun: запуск bun в buncattouchmkdirwhichmvexittruefalseyesseqdirnamebasename
Частично реализовано:
mv: перемещение файлов и каталогов (отсутствует поддержка меж dispositivo)
Еще не реализовано, но запланировано:
- См. Issue #9716 для полного списка.
Утилиты
Bun Shell также реализует набор утилит для работы с оболочками.
$.braces (расширение фигурных скобок)
Эта функция реализует простое расширение фигурных скобок для команд оболочки:
import { $ } from "bun";
await $.braces(`echo {1,2,3}`);
// => ["echo 1", "echo 2", "echo 3"]$.escape (экранирование строк)
Предоставляет логику экранирования Bun Shell как функцию:
import { $ } from "bun";
console.log($.escape('$(foo) `bar` "baz"'));
// => \$(foo) \`bar\` \"baz\"Если вы не хотите, чтобы ваша строка экранировалась, оберните её в объект { raw: 'str' }:
import { $ } from "bun";
await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: команда не найдена: foo
// => bun: команда не найдена: bar
// => bazЗагрузчик файлов .sh
Для простых скриптов оболочки вместо /bin/sh вы можете использовать Bun Shell для запуска скриптов оболочки.
Для этого просто запустите скрипт с помощью bun на файле с расширением .sh.
echo "Hello World! pwd=$(pwd)"bun ./script.shHello World! pwd=/home/demoСкрипты с Bun Shell кроссплатформенны, что означает, что они работают на Windows:
bun .\script.shHello World! pwd=C:\Users\DemoПримечания по реализации
Bun Shell — это небольшой язык программирования в Bun, реализованный на Zig. Он включает рукописный лексер, парсер и интерпретатор. В отличие от bash, zsh и других оболочек, Bun Shell выполняет операции параллельно.
Безопасность в оболочке Bun
По дизайну оболочка Bun не вызывает системную оболочку (как /bin/sh) и вместо этого является переосмыслением bash, которое работает в том же процессе Bun, разработанное с учетом безопасности.
При разборе аргументов команды она рассматривает все интерполированные переменные как одиночные, буквенные строки.
Это защищает оболочку Bun от внедрения команд:
import { $ } from "bun";
const userInput = "my-file.txt; rm -rf /";
// БЕЗОПАСНО: `userInput` рассматривается как одна строка в кавычках
await $`ls ${userInput}`;В приведенном выше примере userInput рассматривается как одна строка. Это заставляет команду ls пытаться прочитать содержимое одного каталога с именем "my-file; rm -rf /".
Соображения безопасности
Хотя внедрение команд предотвращается по умолчанию, разработчики все еще несут ответственность за безопасность в определенных сценариях.
Подобно API Bun.spawn или node:child_process.exec(), вы можете намеренно выполнить команду, которая порождает новую оболочку (например, bash -c) с аргументами.
Когда вы это делаете, вы передаете контроль, и встроенные защиты Bun больше не применяются к строке, интерпретируемой этой новой оболочкой.
import { $ } from "bun";
const userInput = "world; touch /tmp/pwned";
// НЕБЕЗОПАСНО: Вы явно запустили новый процесс оболочки с `bash -c`.
// Эта новая оболочка выполнит команду `touch`. Любой пользовательский ввод,
// переданный таким образом, должен быть тщательно санирован.
await $`bash -c "echo ${userInput}"`;Внедрение аргументов
Оболочка Bun не может знать, как внешняя команда интерпретирует свои собственные аргументы командной строки. Злоумышленник может предоставить ввод, который целевая программа распознает как один из своих собственных параметров или флагов, что приводит к непреднамеренному поведению.
import { $ } from "bun";
// Вредоносный ввод, отформатированный как флаг командной строки Git
const branch = "--upload-pack=echo pwned";
// НЕБЕЗОПАСНО: Хотя Bun безопасно передает строку как один аргумент,
// сама программа `git` видит и действует по вредоносному флагу.
await $`git ls-remote origin ${branch}`;NOTE
**Рекомендация** — Как это является лучшей практикой в каждом языке, всегда санируйте пользовательский ввод перед передачей его в качестве аргумента внешней команде. Ответственность за валидацию аргументов лежит на коде вашего приложения.Благодарности
Большие части этого API были вдохновлены zx, dax и bnx. Спасибо авторам этих проектов.