Bun

$ Shell

Bun Shell makes shell scripting with JavaScript & TypeScript fun. It's a cross-platform bash-like shell with seamless JavaScript interop.

Alpha-quality software: Bun Shell is an unstable API still under development. If you have feature requests or run into bugs, please open an issue. There may be breaking changes in the future.

Quickstart:

import { $ } from "bun";

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

// Use Response as stdin.
await $`echo < ${response} > wc -c`; // 120

Features:

  • Cross-platform: works on Windows, Linux & macOS. Instead of rimraf or cross-env', you can use Bun Shell without installing extra dependencies. Common shell commands like ls, cd, rm are implemented natively.
  • Familiar: Bun Shell is a bash-like shell, supporting redirection, pipes, environment variables and more.
  • Globs: Glob patterns are supported natively, including **, *, {expansion}, and more.
  • Template literals: Template literals are used to execute shell commands. This allows for easy interpolation of variables and expressions.
  • Safety: Bun Shell escapes all strings by default, preventing shell injection attacks.
  • JavaScript interop: Use Response, ArrayBuffer, Blob, Bun.file(path) and other JavaScript objects as stdin, stdout, and stderr.

Getting started

The simplest shell command is echo. To run it, use the $ template literal tag:

import { $ } from "bun";

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

By default, shell commands print to stdout. To quiet the output, call .quiet():

import { $ } from "bun";

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

What if you want to access the output of the command as text? Use .text():

import { $ } from "bun";

// .text() automatically calls .quiet() for you
const welcome = await $`echo "Hello World!"`.text();

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

To get stdout, stderr, and the exit code, use await or .run:

import { $ } from "bun";

const { stdout, stderr, exitCode } = await $`echo "Hello World!"`.quiet();

console.log(stdout); // Buffer(6) [ 72, 101, 108, 108, 111, 32 ]
console.log(stderr); // Buffer(0) []
console.log(exitCode); // 0

Redirection

A command's input or output may be redirected using the typical Bash operators:

  • < redirect stdin
  • > or 1> redirect stdout
  • 2> redirect stderr
  • &> redirect both stdout and stderr
  • >> or 1>> redirect stdout, appending to the destination, instead of overwriting
  • 2>> redirect stderr, appending to the destination, instead of overwriting
  • &>> redirect both stdout and stderr, appending to the destination, instead of overwriting
  • 1>&2 redirect stdout to stderr (all writes to stdout will instead be in stderr)
  • 2>&1 redirect stderr to stdout (all writes to stderr will instead be in stdout)

Bun Shell also supports redirecting from and to JavaScript objects.

Example: Redirect output to JavaScript objects (>)

To redirect stdout to a JavaScript object, use the > operator:

import { $ } from "bun";

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

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

The following JavaScript objects are supported for redirection to:

  • Buffer, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, ArrayBuffer, SharedArrayBuffer (writes to the underlying buffer)
  • Bun.file(path), Bun.file(fd) (writes to the file)

Example: Redirect input from JavaScript objects (<)

To redirect the output from JavaScript objects to stdin, use the < operator:

import { $, file } 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

The following JavaScript objects are supported for redirection from:

  • Buffer, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, ArrayBuffer, SharedArrayBuffer (reads from the underlying buffer)
  • Bun.file(path), Bun.file(fd) (reads from the file)
  • Response (reads from the body)

Example: Redirect stdin -> file

import { $ } from "bun"

await $`cat < myfile.txt`

Example: Redirect stdout -> file

import { $ } from "bun"

await $`echo bun! > greeting.txt`

Example: Redirect stderr -> file

import { $ } from "bun"

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

Example: Redirect stdout -> stderr

import { $ } from "bun"

// redirects stderr to stdout, so all output
// will be available on stdout
await $`bun run ./index.ts 2>&1`

Example: Redirect stderr -> stdout

import { $ } from "bun"

// redirects stdout to stderr, so all output
// will be available on stderr
await $`bun run ./index.ts 1>&2`

Piping (|)

Like in bash, you can pipe the output of one command to another:

import { $ } from "bun";

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

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

You can also pipe with JavaScript objects:

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

Environment variables

Environment variables can be set like in bash:

import { $ } from "bun";

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

You can use string interpolation to set environment variables:

import { $ } from "bun";

const foo = "bar123";

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

Input is escaped by default, preventing shell injection attacks:

import { $ } from "bun";

const foo = "bar123; rm -rf /tmp";

await $`FOO=${foo} bun -e 'console.log(process.env.FOO)'`; // bar123; rm -rf /tmp\n

Changing the environment variables

By default, process.env is used as the environment variables for all commands.

You can change the environment variables for a single command by calling .env():

import { $ } from "bun";

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

You can change the default environment variables for all commands by calling $.env:

import { $ } from "bun";

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

// the globally-set $FOO
await $`echo $FOO`; // bar

// the locally-set $FOO
await $`echo $FOO`.env({ FOO: "baz" }); // baz

You can reset the environment variables to the default by calling $.env() with no arguments:

import { $ } from "bun";

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

// the globally-set $FOO
await $`echo $FOO`; // bar

// the locally-set $FOO
await $`echo $FOO`.env(undefined); // ""

Changing the working directory

You can change the working directory of a command by passing a string to .cwd():

import { $ } from "bun";

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

You can change the default working directory for all commands by calling $.cwd:

import { $ } from "bun";

$.cwd("/tmp");

// the globally-set working directory
await $`pwd`; // /tmp

// the locally-set working directory
await $`pwd`.cwd("/"); // /

Reading output

To read the output of a command as a string, use .text():

import { $ } from "bun";

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

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

Reading output as JSON

To read the output of a command as JSON, use .json():

import { $ } from "bun";

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

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

Reading output line-by-line

To read the output of a command line-by-line, use .lines():

import { $ } from "bun";

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

You can also use .lines() on a completed command:

import { $ } from "bun";

const search = "bun";

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

Reading output as a Blob

To read the output of a command as a Blob, use .blob():

import { $ } from "bun";

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

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

Builtin Commands

For cross-platform compatibility, Bun Shell implements a set of builtin commands, in addition to reading commands from the PATH environment variable.

  • cd: change the working directory
  • ls: list files in a directory
  • rm: remove files and directories
  • echo: print text
  • pwd: print the working directory
  • bun: run bun in bun

Partially implemented:

  • mv: move files and directories (missing cross-device support)

Not implemented yet, but planned:

  • mkdir: create directories
  • cp: copy files and directories
  • cat: concatenate files

Utilities

Bun Shell also implements a set of utilities for working with shells.

$.braces (brace expansion)

This function implements simple brace expansion for shell commands:

import { $ } from "bun";

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

$.escape (escape strings)

Exposes Bun Shell's escaping logic as a function:

import { $ } from "bun";

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

If you do not want your string to be escaped, wrap it in a { raw: 'str' } object:

import { $ } from "bun";

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

.bun.sh file loader

For simple shell scripts, instead of sh, you can use Bun Shell to run shell scripts.

To do that, run any file with bun that ends with .bun.sh:

echo "echo Hello World!" > script.bun.sh
bun ./script.bun.sh
> Hello World!

On Windows, Bun Shell is used automatically to run .sh files when using Bun:

echo "echo Hello World!" > script.sh
# On windows, .bun.sh is not needed, just .sh
bun ./script.sh
> Hello World!

Credits

Large parts of this API were inspired by zx, dax, and bnx. Thank you to the authors of those projects.