Bun Shell is a cross-platform, bash-like shell built into Bun that lets you run shell commands from JavaScript and TypeScript using tagged template literals.
Bun Shell makes shell scripting with JavaScript and TypeScript ergonomic and safe. It is a cross-platform bash-like shell with seamless JavaScript interop — no extra dependencies required.
import { $ } from "bun";const response = await fetch("https://example.com");// Use a Response object as stdinawait $`cat < ${response} | wc -c`; // 1256
Use $.nothrow() or $.throws(false) to change the default behavior for all commands:
import { $ } from "bun";// All commands will not throw on non-zero exit$.nothrow(); // equivalent to $.throws(false)await $`something-that-may-fail`; // No exception thrown// Restore default behavior$.throws(true);
Pipe the output of one command to another using |, just like in bash:
import { $ } from "bun";const result = await $`echo "Hello World!" | wc -w`.text();console.log(result); // 2\n
You can also pipe from 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
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
Set environment variables inline just like in bash:
import { $ } from "bun";await $`FOO=bar bun -e 'console.log(process.env.FOO)'`; // bar
Interpolated values are escaped automatically, preventing injection:
import { $ } from "bun";const userInput = "bar; rm -rf /tmp";// SAFE: userInput is treated as a single literal stringawait $`FOO=${userInput} bun -e 'console.log(process.env.FOO)'`;// => bar; rm -rf /tmp
Use the $(...) syntax for command substitution. Due to how Bun uses the raw property on template literals, the backtick syntax (`...`) does not work for nested command substitution.
Bun Shell ships native implementations of common commands for cross-platform compatibility:cd, ls, rm, echo, pwd, bun, cat, touch, mkdir, which, mv, exit, true, false, yes, seq, dirname, basenameAny command not in this list is looked up in the system PATH.
Bun Shell does not invoke a system shell like /bin/sh. It is a re-implementation that treats all interpolated variables as single, literal strings, preventing command injection:
import { $ } from "bun";const userInput = "my-file.txt; rm -rf /";// SAFE: userInput is treated as a single quoted stringawait $`ls ${userInput}`;
Spawning a new shell process bypasses Bun’s protections. If you use bash -c with interpolated user input, you are responsible for sanitizing that input:
// UNSAFE: the new bash process interprets the string as shell syntaxconst userInput = "world; touch /tmp/pwned";await $`bash -c "echo ${userInput}"`;
Argument injection is not prevented. Bun passes strings as single arguments, but the target program may interpret them as its own flags:
const branch = "--upload-pack=echo pwned";// Bun safely passes the string, but git acts on the malicious flagawait $`git ls-remote origin ${branch}`;
Always validate and sanitize user-provided input before passing it to external commands.