Bun Shell makes shell scripting with JavaScript & TypeScript fun. It's a cross-platform bash-like shell with seamless JavaScript interop.
Quickstart:
import { $ } from "bun";
const response = await fetch("https://example.com");
// Use Response as stdin.
await $`cat < ${response} | wc -c`; // 1256
- Cross-platform: works on Windows, Linux & macOS. Instead of
rimraf
orcross-env
', you can use Bun Shell without installing extra dependencies. Common shell commands likels
,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. - Shell scripting: Bun Shell can be used to run shell scripts (
.bun.sh
files). - Custom interpreter: Bun Shell is written in Zig, along with it's lexer, parser, and interpreter. Bun Shell is a small programming language.
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
By default, await
ing will return stdout and stderr as Buffer
s.
import { $ } from "bun";
const { stdout, stderr } = await $`echo "Hello World!"`.quiet();
console.log(stdout); // Buffer(6) [ 72, 101, 108, 108, 111, 32 ]
console.log(stderr); // Buffer(0) []
By default, non-zero exit codes will throw an error. This ShellError
contains information about the command run.
import { $ } from "bun";
try {
const output = await $`something-that-may-fail`.text();
console.log(output);
} catch (err) {
console.log(`Failed with code ${err.exitCode}`);
console.log(err.stdout.toString());
console.log(err.stderr.toString());
}
Throwing can be disabled with .nothrow()
. The result's exitCode
will need to be checked manually.
import { $ } from "bun";
const { stdout, stderr, exitCode } = await $`something-that-may-fail`
.nothrow()
.quiet();
if (exitCode !== 0) {
console.log(`Non-zero exit code ${exitCode}`);
}
console.log(stdout);
console.log(stderr);
The default handling of non-zero exit codes can be configured by calling .nothrow()
or .throws(boolean)
on the $
function itself.
import { $ } from "bun";
// shell promises will not throw, meaning you will have to
// check for `exitCode` manually on every shell command.
$.nothrow(); // equivilent to $.throws(false)
// default behavior, non-zero exit codes will throw an error
$.throws(true);
// alias for $.nothrow()
$.throws(false);
await $`something-that-may-fail`; // No exception thrown
A command's input or output may be redirected using the typical Bash operators:
<
redirect stdin>
or1>
redirect stdout2>
redirect stderr&>
redirect both stdout and stderr>>
or1>>
redirect stdout, appending to the destination, instead of overwriting2>>
redirect stderr, appending to the destination, instead of overwriting&>>
redirect both stdout and stderr, appending to the destination, instead of overwriting1>&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.
To redirect stdout to a JavaScript object, use the >
operator:
import { $ } from "bun";
const buffer = Buffer.alloc(100);
await $`echo "Hello World!" > ${buffer}`;
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)
To redirect the output from JavaScript objects to stdin, use the <
operator:
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
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)
import { $ } from "bun";
await $`cat < myfile.txt`;
import { $ } from "bun";
await $`echo bun! > greeting.txt`;
import { $ } from "bun";
await $`bun run index.ts 2> errors.txt`;
import { $ } from "bun";
// redirects stderr to stdout, so all output
// will be available on stdout
await $`bun run ./index.ts 2>&1`;
import { $ } from "bun";
// redirects stdout to stderr, so all output
// will be available on stderr
await $`bun run ./index.ts 1>&2`;
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 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
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); // ""
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("/"); // /
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
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" }
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);
}
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" }
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 directoryls
: list files in a directoryrm
: remove files and directoriesecho
: print textpwd
: print the working directorybun
: run bun in buncat
touch
mkdir
which
mv
exit
true
false
yes
seq
dirname
basename
Partially implemented:
mv
: move files and directories (missing cross-device support)
Not implemented yet, but planned:
- See #9716 for the full list.
Bun Shell also implements a set of utilities for working with shells.
This function implements simple brace expansion for shell commands:
import { $ } from "bun";
await $.braces(`echo {1,2,3}`);
// => ["echo 1", "echo 2", "echo 3"]
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
For simple shell scripts, instead of /bin/sh
, you can use Bun Shell to run shell scripts.
To do so, just run the script with bun
on a file with the .sh
extension.
echo "Hello World! pwd=$(pwd)"
$ bun ./script.sh
Hello World! pwd=/home/demo
Scripts with Bun Shell are cross platform, which means they work on Windows:
> bun .\script.sh
Hello World! pwd=C:\Users\Demo
Bun Shell is a small programming language in Bun that is implemented in Zig. It includes a handwritten lexer, parser, and interpreter. Unlike bash, zsh, and other shells, Bun Shell runs operations concurrently.
Large parts of this API were inspired by zx, dax, and bnx. Thank you to the authors of those projects.