Open-Source Wikis

/

Bun

/

Apps

/

Shell

oven-sh/bun

Shell

Active contributors: Jarred Sumner, Dylan Conway

Bun.$ is an embedded cross-platform shell — POSIX-ish syntax that runs on Windows without cmd.exe/PowerShell/Git-Bash and on POSIX without invoking /bin/sh. The implementation lives entirely in src/shell/.

Purpose

  • Execute pipelines, redirections, command substitutions, and globbing portably.
  • Avoid the cost and pitfalls of spawning a real shell subprocess for one-shot commands.
  • Integrate with the JavaScript runtime: await $\echo hello`returns aShellOutput` JS object.
  • Implement the bun -e '...' shell mode where Bun acts like bash -c.

Directory layout

src/shell/
├── shell.zig            # the parser + AST (~175 KB)
├── interpreter.zig      # the runtime (~82 KB)
├── braces.zig           # brace expansion (`{a,b}`)
├── states/              # state machines per construct (pipeline, if, command, ...)
├── builtin/             # cd, echo, mv, rm, true, false, ... implemented in Zig
├── Builtin.zig          # builtin dispatch
├── ParsedShellScript.zig
├── EnvMap.zig           # cross-platform env handling (Windows is case-insensitive)
├── EnvStr.zig
├── IO.zig / IOReader.zig / IOWriter.zig  # async stdin/stdout/stderr
├── Yield.zig            # cooperative-yield primitive
├── RefCountedStr.zig
├── subproc.zig          # subprocess management
└── util.zig

Key abstractions

Type File Purpose
Shell parser src/shell/shell.zig Tokenises and parses a script into an AST: pipelines, simple commands, redirections, expansions.
Interpreter src/shell/interpreter.zig Runs the AST against the event loop. Each construct is a state machine.
State machines src/shell/states/ One file per AST node: cmd.zig, pipeline.zig, if.zig, subshell.zig, script.zig, ...
Yield src/shell/Yield.zig A cooperative async primitive — tells the interpreter to suspend and resume when an I/O completes.
IOReader / IOWriter src/shell/IOReader.zig, IOWriter.zig Non-blocking pipes wired into the event loop.
Builtin src/shell/Builtin.zig, src/shell/builtin/ In-process implementations of common commands (cd, echo, mv, rm, cat, pwd, which, printf, seq, false, true, ...).
ParsedShellScript src/shell/ParsedShellScript.zig Cached parsed form of a script template literal.

Pipeline execution

graph TD
    Source["$\`a | b > c.txt\`"] --> Lexer
    Lexer --> Parser
    Parser --> AST[shell AST]
    AST --> Init[Interpreter.init]
    Init --> Top[script state]
    Top --> Pipe[pipeline state]
    Pipe --> CmdA[cmd state: a]
    Pipe --> CmdB[cmd state: b]
    CmdA -->|stdout pipe| CmdB
    CmdB -->|stdout fd| File[c.txt]
    CmdA --> Yield1[Yield to event loop]
    CmdB --> Yield2[Yield to event loop]

Each state owns its inputs and outputs and emits Yields to suspend. The interpreter loop is essentially a coroutine driver tied to the JS event loop, so Bun.$ doesn't block JS execution.

Builtins vs spawned commands

The shell prefers in-process builtins. The list is in src/shell/Builtin.zig. Examples: cd, pwd, echo, printf, mkdir, rm, rmdir, mv, cp, cat, which, true, false, [, seq, read, export, unset, dirname, basename, yes, expr, exit, kill, touch, ls. These are noticeably faster than fork+exec and work uniformly on Windows.

If a name doesn't match a builtin, the shell looks it up on $PATH and spawns it via subproc.zig (which uses Bun's normal spawn machinery: posix_spawn on POSIX, CreateProcessW on Windows).

Expansions

The parser supports:

Expansion Implementation
$VAR, ${VAR} parser + EnvMap.zig
~, ~user interpreter.zig
$(cmd) spawn a sub-interpreter, capture stdout
`cmd` same as $(cmd)
*, ?, [abc] glob src/glob/ (bun.glob)
{a,b,c} braces src/shell/braces.zig
>, >>, <, 2>, &>, here-docs interpreter.zig + IO*.zig
&&, ||, ; parser + states/

Brace expansion happens before glob expansion. Globs are evaluated via bun.glob (src/glob/) which is the same engine that backs Bun.Glob in JavaScript.

Cross-platform env

EnvMap.zig and EnvStr.zig handle the Windows quirk that environment variables are case-insensitive but case-preserving. Lookups normalise to uppercase on Windows and stay verbatim on POSIX. The shell's view of process.env is an EnvMap, not a std.StringHashMap.

Path semantics are also handled: pipelines on Windows use the same \\.\\pipe\\... named pipes that uSockets uses elsewhere; redirections honour Windows' open-mode flags and reserved file names (CON, NUL, ...).

JavaScript integration

The user-facing Bun.$ API is in src/js/bun/$.ts plus the host bindings in src/bun.js/api/Shell.classes.ts / src/bun.js/api/ParsedShellScript.classes.ts. A tagged template literal is parsed once, cached as a ParsedShellScript, and re-executed with fresh interpolations. The result is a ShellOutput with stdout, stderr, exitCode.

Streaming consumption (for await (const chunk of $\...`.lines())) is supported by piping the interpreter's stdout into a JS ReadableStream`.

bun -e '<shell>' mode

bun -e runs the argument as JS. To run as shell, use bun --shell. The dispatcher in src/cli.zig and src/cli/exec_command.zig decides which interpreter to invoke.

Integration points

  • Event loopYield suspends the interpreter; the loop schedules continuation when an I/O completes.
  • Subprocesssrc/shell/subproc.zig calls into bun.spawn (src/bun.js/api/bun/process.zig).
  • Globsrc/glob/ is the same engine used by Bun.Glob.
  • Filesystembun.sys.* for mv, cp, rm, mkdir builtins.
  • Encodingsrc/string/ for case-insensitive matching and Latin-1 / UTF-16 round-trips.

Entry points for modification

  • To add a builtin, drop <name>.zig in src/shell/builtin/ and register it in src/shell/Builtin.zig. The builtin should expose a state struct that the interpreter can drive.
  • To support a new shell construct, extend the parser in src/shell/shell.zig and add a state in src/shell/states/.
  • To change globbing semantics, edit src/glob/.

Key source files

File Purpose
src/shell/shell.zig Lexer + parser + AST.
src/shell/interpreter.zig Runtime driver.
src/shell/states/ One file per AST node kind.
src/shell/Builtin.zig and src/shell/builtin/ In-process commands.
src/shell/IOReader.zig, src/shell/IOWriter.zig Async I/O.
src/shell/EnvMap.zig Cross-platform env.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Shell – Bun wiki | Factory