>_ZSHRS REFERENCE
The most powerful shell ever created. No-fork architecture, AOP intercept, worker thread pool, bytecode caching, fusevm compiled execution.
SHELL SCRIPTS AT MACHINE CODE SPEED.
NO FORK. NO INTERPRETER. JUST BYTECODES.
For the first time in the history of computing — since the dawn of Unix at Bell Labs
in 1970 — a shell compiles to executable bytecodes and JITs them to native machine code.
Nushell got to bytecode first (its IR evaluator shipped in 0.96.0 and became the default in
0.98.0), but interprets it; zshrs runs its bytecode on a VM with fused superinstructions and
hands hot blocks to Cranelift. Every interactive command line, every shell script,
every function invocation, every source’d file compiles to fusevm bytecodes
and executes at near-machine-code speed. No tree-walking interpreter. No fork. No parsing at
runtime if bytecodes are cached.
Compiled bytecodes are cached as rkyv zero-copy archives mmap'd directly into
memory. Function invocations deserialize cached bytecodes
in microseconds — skipping the lexer, parser, and compiler entirely. source’d
scripts and interpreted scripts follow the same path: compile once, cache, execute from bytecode
forever. No other shell persists its bytecode: zsh’s
.zwc is per-file wordcode for zsh’s own interpreter, and Nushell’s IR is
rebuilt on every parse and dropped at process exit.
[0x00a] INVENTIONS — THE LIST
Sixty-four candidates, filtered by three tests that all have to
pass: it exists (something in the tree does this,
with a name you can type), nobody else has it (not
“ours is compiled” — no other shell does the thing
at all, and the near misses are named), and it is an idea,
not a decision (a file layout, a default or a command name
is a choice; an idea is something another project could inherit and
be changed by). Twenty-nine survive. The cuts — filing
conventions, flags, mechanisms of other entries, and one claim that
was simply false — are listed with reasons in
docs/INVENTIONS.md,
which is the canonical register.
This is a legacy, not a battle. Every item below originated in
zshrs and is offered as prior art for the shell-design
commons under the MIT grant. Future shells (bash, fish,
nushell, elvish, oil, xonsh, murex, projects that don’t exist
yet) should inherit any of it. Ports must credit zshrs as the
invention source in their docs — see
CREATORS.md
for the suggested wording. The protected invariants in
MAINTAINERS.md
guard upstream identity, not the ideas.
Execution
Bytecode, JIT, and persistence across processes
Every command, script, function and sourced file lowers to fusevm bytecode; hot blocks go through Cranelift to x86-64/aarch64. Nushell reached bytecode first (IR in 0.96.0, default in 0.98.0) but interprets it, rebuilds per parse, and drops it at exit; zsh’s .zwc is wordcode for zsh’s own interpreter. What is left after subtracting both: first shell to emit native machine code, first to persist general bytecode across processes.
Native code persisted across launches
The .fjit disk cache keeps JIT output warm between invocations, so hot chunks are not recompiled per process. Android ART’s .oat, .NET ReadyToRun and JVM AppCDS have done this for years — its absence from every shell is the notable part.
AOT completion corpus keyed on compiler identity
--prewarm-autoloads compiles every $fpath directory so a fresh install’s first <TAB> is a shard probe, not a parse. Entries are stamped with the fpath directory, a SHA-256 of the definition text, and the identity of the binary that compiled it — standard in Nix, ccache and Bazel, absent from every shell cache. Deliberately bypassed for ksh_autoload and autoload without -U, where the compiled program is not a function of the file’s bytes alone.
JIT tier introspection as a diagnosis
--tiers runs a script, asks fusevm’s own predicates which tier took each chunk, and for chunks that reached neither, lists the op kinds responsible. Compilers have had optimization remarks for years; a shell telling you why your loop stayed interpreted is new.
The anti-fork architecture
A persistent pool of 2–18 threads takes command substitution, process substitution, globbing, completion and autoloading; 23 coreutils commands run in-process so a cat in a pipeline never leaves the shell. Baumann, Appavoo, Krieger and Roscoe’s “A fork() in the road” (HotOS 2019), applied to a shell. The 2000–5000x figure is fork overhead divided by a function call — a fixed cost disappearing, not a benchmark.
Parallelism as grammar rather than library
pmap, pgrep, peach, barrier, async and await are VM-dispatched builtins (fusevm IDs 210–215), not library calls: the body compiles once and runs on pooled VMs with zero forks. make -j, xargs -P and GNU parallel all sit outside the shell language. This is inside it.
State
Recorder-owns-rebuild
A separate binary sources your real login chain while AOP interception sits on every state-mutating dispatcher — alias, function definition, export, fpath edit, hash -d, zstyle, bindkey, compdef, zmodload, setopt, trap, sched, source, assignment — recording kind, name, value, file, line and call chain. Every other shell discovers your config by static walking and hoping its parse matches the shell’s. The DTrace insight applied to configuration: watch what runs instead of reasoning about the source. It is why zwhere can name the file and line that defined any alias you have.
Cacheable configuration vs. configuration that must be replayed
replay/ holds the non-deterministic .zshrc fragments. Everything the recorder can prove is a pure function of your dotfiles folds into the canonical shard and is skipped at startup; the rest is replayed. Purity analysis on shell configuration is the load-bearing idea under the cold-start number.
Singleton daemon owning every mutation
Canonical state, fsnotify, scheduling, jobs, locks, cache, history — one writer, stateless forkable clients, no auto-spawn. The pattern is emacsclient, Nailgun and LSP; the application to a shell’s own state is new.
Session-persistent jobs with bidirectional ptmx attach
zjob submit --pty opens a pseudo-terminal and runs the child under the daemon. Attaching later puts your terminal in raw mode, pumps stdin in, drains output back, forwards SIGWINCH, detaches on Ctrl-]. Collapses nohup + screen + pueue + disown, and solves what none of them solve cleanly: a background job blocking on a prompt you cannot see.
Cross-shell pub/sub and token-issued named locks
zsubscribe / zpublish / zlock as builtins routed through the daemon, replacing hand-glued flock + socat + FIFOs. Release requires the original token, so a dead shell cannot free someone else’s lock.
Observability
Value lineage at the bytecode level (provenance)
An origin (command substitution, glob, heredoc, process substitution) plus every op that touched the value — expand, concat, assign, exec, call — each stamped with file, line, enclosing function and wall clock. Functions carry the same chain: definition, every redefinition, every call at the caller’s line, the unfunction that ended it. One relaxed atomic load when disarmed. typeset -p answers what a value is; set -x answers what ran; nothing answers where the bytes came from. Weiser’s slicing (1981) is static, Perl’s taint mode is one bit, PASS (2006) instruments the storage layer, W3C PROV standardizes the model — all outside the shell, which is the layer that glues the tools together. See PROVENANCE.md.
Aspect-oriented advice on any command or function
intercept before/after/around with glob patterns, nanosecond timing and intercept_proceed — one primitive subsuming defer, profiling, memoization, retry and timeout. The vocabulary is forty-five years old (Flavors’ before/after methods, CLOS :around in 1988, AspectJ in 1997) and no shell had offered it as anything but hand-rolled function wrapping.
The shell describing itself to a program
--dump-tokens, --dump-wordcode, --dump-ast, --disasm, plus --dump-reflection for a JSON self-description of builtins, keywords and options. This is javap, Python’s dis and perl -MO=Deparse for a shell; no Bourne-family shell can do it.
Language
Sigil dispatch to a second language sharing the VM
@{} routes to embedded stryke, compiling to the same fusevm ops the shell does — including inside AOP advice. Multi-language runtimes are the JVM and CLR story, and Racket’s #lang is the closest analogue for inline dispatch, but no shell has hosted a second language in its own grammar.
A grammar extension with a switch that makes it vanish
intercept … { } is a real lexer extension, since zsh cannot parse a bare } as an argument. The lexer captures the span as raw source, so the body keeps its own redirections, pipelines and heredocs, and nothing expands until the advice fires. Under --zsh the extension is off and the form is rejected exactly as /bin/zsh rejects it. This is how you add syntax to a language you have also promised to be compatible with.
Extensibility
A stable, versioned, independently-published plugin ABI
cargo add znative, ship a cdylib, load it with zmodload -R, version-gated with mismatches refused, only #[repr(C)] crossing the boundary; plugins register builtins and native completion generators wired into compsys. bash’s enable -f and zsh’s zmodload load native code too, but only against the shell’s private headers with no stable ABI and no version gate — which is why neither has third-party native plugins. Apache, nginx, PostgreSQL, Redis and Emacs 25 all published versioned boundaries and got ecosystems.
Absorbing foreign binaries into the shell process
git served as a builtin with no fork, exec or PATH lookup, and an fzf-compatible finder honoring FZF_DEFAULT_OPTS in-process. BusyBox absorbed the coreutils in 1995 and dispatched on argv[0]; this extends that logic past coreutils into full applications. Near misses, named: BusyBox has no git applet, Nushell’s gstat is status-only in a child process, git-shell execs real git, and Elvish’s in-process fuzzy modes are shell UI rather than a pipeline filter.
Compatibility
Two axes of emulation fidelity, both available
--sh reproduces /bin/sh; --sh --zsh reproduces zsh’s model of /bin/sh. The bare POSIX modes use XSI echo where zsh’s emulate sh sets BSD_ECHO, and --bash accepts bash’s own set -o names including the six zsh has no option for. Eight Bourne-family dialects on one bytecode core, each verified against its actual reference shell rather than against zsh’s approximation. No precedent found for offering both notions of fidelity as distinct modes.
A hybrid port: native spine, interpreted leaves, one tree
src/compsys/ported/ mirrors zsh’s Completion/ layout exactly, but engine functions are ported to Rust with citations while end-user completers are copied verbatim alongside them — same filenames, dispatched through a _call_function bridge, 1,240 files under that tree. The shape is a JIT with an interpreter fallback, applied to a script framework.
Inheriting the configuration vocabulary of what you replace
The native ZLE engines are fish’s Rust ports, but the palette and word-level classification are fast-syntax-highlighting’s, and ZSH_HIGHLIGHT_STYLES, ZSH_AUTOSUGGEST_*, HISTORY_SUBSTRING_SEARCH_* and AUTOPAIR_* apply unchanged. The p10k engine does the same with .p10k.zsh. The rule — when replacing a widely-configured userspace layer with a native one, adopt its config surface so nobody migrates — appears twice in the codebase.
Absorbing the prompt theme into the binary
powerlevel10k as 14 files and 15,479 lines of in-process Rust, segment builders cited line-by-line against the theme spec, gitstatusd replaced by a native .git reader, your .p10k.zsh sourced unchanged. The framing generalizes: instant prompt exists to hide the cost of interpreting the theme, so when interpretation stops the workaround is deleted rather than ported.
Wall-clock budgets on per-keystroke rendering
Highlight and autosuggest passes are capped at 8 ms by default, so a huge directory or a pathological $PATH cannot lag typing. Soft-real-time scheduling applied to a line editor. Shell plugins have historically had no budget at all, which is why a slow git status in a prompt hangs the terminal.
Verification
Architectural invariants enforced mechanically in CI
tests/port_purity.rs freezes the port directory: mirror a real Src/<x>.c, cite the C function, no new files, no invented helper names. tests/tree_walker_absent.rs asserts at source level that the old interpreter is still gone, backed by 174 behavioral pins. Contributors and code-generation tools both invent helpers and both create files to hold them; both vectors are closed by a test rather than by review. This is the mechanism that makes a large rewrite finishable.
Differential fuzzing a shell against a reference implementation
Grammar-driven, seed-replayable snippets per mode, run through both zshrs and real zsh, divergences re-confirmed three times so load flakes cannot register: 22,200 cases across 74 modes, 27 divergences at 0.12%, 71 of 74 modes at zero, each remaining mode traced to one named root cause. McKeeman named differential testing in 1998, Csmith made it standard for compilers in 2011, jsfunfuzz for JS engines. Nobody had aimed it at a shell.
Tooling
A source formatter in the shell binary
--fmt: block reindent, idiomatic spacing per the zsh and OMZ style guides, heredoc-safe, idempotent, stdin-to-stdout or in-place. The same engine backs the LSP’s formatting capability, so editor and CLI cannot drift. gofmt made this a language-community expectation in 2009; shells got shfmt, a third-party Go program.
Live completion inside the editor
The LSP does not stop at builtins and in-file functions: it answers from the real compsys completers, reading the recorded environment from the canonical shard, so git ch completes checkout and git checkout lists actual branches — in your editor. Shell-out completers are killed at the request deadline and never touch the editor’s stdio. Because the server is the shell, it answers from the same lexer, parser, option table and completers the interactive session uses, instead of a reimplemented parser in a separate Node project.
First Bourne-lineage shell with LSP and DAP in its own binary
Scoped to what survives a prior-art check. Elvish shipped elvish -lsp in 0.18.0 on 2022-03-20 and Nushell shipped nu --lsp in 0.87.0 on 2023-11-14, so “world’s first shell with a language server” is false; Endo v0.1.0 shipped endo --lsp and endo --dap together on 2026-04-08, before zshrs’s two modules landed on 2026-05-16, so “first with both” is false too. All three are outside the Bourne lineage. What holds: first shell in the Bourne lineage with either, and with both. bash, zsh, ksh, dash and fish ship neither — bash-language-server and fish-lsp are separate Node/TypeScript packages and vscode-bash-debug drives the external bashdb script. Alongside it, --dump-plugins surfaces every sourced plugin (zinit / oh-my-zsh / prezto / antidote / antigen / zplug) under the IDE’s External Libraries node — cmd-clickable, find-usages-able, renameable across plugin boundaries. No shell of any lineage exposes its plugin-manager state to an IDE.
A unit-test framework and runner in the binary
Fourteen assertion verbs plus a worker-pool runner: one persistent subprocess per CPU, fork-on-receive per test, JSON over pipes, per-test fd 2 capture so concurrent workers cannot tear each other’s output. Bounded to POSIX-compatible shells — Pester is named and excluded as non-POSIX, Nushell’s std assert named and excluded for lacking a runner. bats-core, shunit2, Bach, ShellSpec and zunit are all separate installs.
Full enumeration + suggested attribution wording in
CREATORS.md.
Op-by-op detail in daemon-report.html.
Port-coverage report in report.html.
[0x00] THE COMPILATION PIPELINE
Every path through zshrs ends at the same place: fusevm bytecode execution.
| Path | Lex | Parse | Compile | Execute | Cache |
|---|---|---|---|---|---|
| Interactive command | Yes | Yes | Yes | fusevm | No (ephemeral) |
| Script file (first run) | Yes | Yes | Yes | fusevm | Yes → rkyv |
| Script file (cached) | No | No | No | fusevm | Hit → deserialize |
| Autoload function (cached) | No | No | No | fusevm | Hit → deserialize |
| Plugin source (cached) | No | No | No | fusevm | Delta replay → µs |
[0x01] ARCHITECTURE
Source-tree layout: ported / extensions / recorder split
The runtime crate is physically split so port code and original code can never be confused. Bots, contributors, and humans all read docs/PORT.md before writing a single line of code; tests/port_purity.rs mechanically enforces the rules in CI.
src/ported/— 106 files, FROZEN. Strict 1:1 port. Every.rsmirrors a realsrc/zsh/Src/<x>.cfile (same stem, same relative subpath). Every top-levelfncarries a/// Port of <cname>() from Src/<file>.c:NNNNdoc-comment. New file creation is banned; new fn names that don't exist in upstream zsh C source are banned. The freeze closes both drift vectors: bots invent helper names ("shell_quote", "find_in_path") and bots create fresh files to drop helpers in. Both blocked.src/extensions/— 102 files. The non-port directory. Features zsh C demonstrably does not have: AOT (aot.rs,compile_zsh.rs), autoload/plugin/script caches, fish-style autosuggest/abbrev/highlight, the native powerlevel10k engine (p10k/, 21 files), persistent worker pool, arith JIT, AST s-exp dump, ZWC byte-code helpers, recorder hooks, daemon presence, structured logging, ZLE keymaps/widgets, ext builtins, regex module, config, overlay snapshot + canonical apply, subscript/fds/hist extensions.port_purityexempts this directory from the 1:1 file-existence rule on the basis that no C ancestor exists.src/recorder/— 1 file, feature-gated. Every symbol#[cfg(feature = "recorder")]; deleted by rustc when the feature is off. Compiled into the separatezshrs-recorderbinary, never the defaultzshrsbuild.src/zsh/— vendored upstream. Read-only reference. The C spec; never modified.
Lexer + parser ports live in src/ported/lex.rs and src/ported/parse.rs (no separate parse/ crate). Workspace siblings: daemon/ (41 files, zshrs-daemon), znative/ (1 file, the published plugin-ABI SDK), and runtime/ (1 file, zshrs-runtime — emits libzsh.a for AOT linking without making cargo install zshrs build it) — [workspace] members = [".", "daemon", "znative", "runtime"] in root Cargo.toml; compsys was folded into the main zshrs crate (no longer a standalone sibling). Vendored: vendor/fish/ (157 files, fish-style reader/highlighter/abbreviations), bins/ (5 entry points: zshrs, zshrs-recorder with required-features = ["recorder"], zd with required-features = ["zd"], bench-autoload, parity-fuzz with required-features = ["parity-fuzz"]). 861 .rs files total · 915,020 Rust LOC (git ls-files '*.rs' | xargs wc -l, 2026-08-29). Test count: 19,916 #[test] markers across the tree (rg '^\s*#\[test\]', the same anchored count the engineering report prints).
[0x01] ANTI-FORK ARCHITECTURE
Every fork is a full process copy. On macOS, fork() costs 2-5ms
including exec + ld.so + libc init. zsh forks for every
$(...), <(...), cat, grep, subshell, and completion.
zshrs forks for none of them.
| Operation | zsh | zshrs | Speedup |
|---|---|---|---|
cat file | fork + exec /bin/cat | Builtin — zero fork | 2000-5000x |
head/tail/wc | fork + exec | Builtin — zero fork | 2000-5000x |
sort/find/uniq | fork + exec | Builtin — zero fork | 2000-5000x |
date/hostname/uname | fork + exec | Direct syscall | 3000-8000x |
sleep/mktemp/touch | fork + exec | Builtin — zero fork | 2000-5000x |
xattr operations | fork + exec xattr | Direct syscall | 2000-5000x |
pmap/pgrep/peach | fork N times to sh -c | VM execution — zero fork | Nx |
$(cmd) | fork + pipe + exec | In-process stdout capture via dup2 | — |
<(cmd) / >(cmd) | fork + FIFO | Worker pool thread + FIFO | — |
Glob **/*.rs | Single-threaded opendir | Parallel walkdir per-subdir on pool | — |
rehash | Serial readdir per PATH dir | Parallel scan across pool | — |
| Autoload function | Read file + parse every time | Zero-copy mmap of rkyv-archived bytecode (µs) | 100x |
Coreutils Builtins (23 commands, zero fork)
Every invocation of these commands is 2000-5000x faster than forking to the external binary.
[0x02] WORKER THREAD POOL
Persistent pool of warm threads. Bounded crossbeam channel with backpressure. Panic recovery keeps workers alive. Task cancellation on Ctrl-C. Instant shutdown on exit.
Tasks shipped to the pool:
compinit
Background fpath scan + bytecode compilation of 16K+ functions
Process Sub
<(cmd) and >(cmd) on pool threads instead of fork
Parallel Glob
**/ recursive walk split per-subdir across pool
Metadata Prefetch
Glob qualifiers: one parallel stat batch, zero syscalls after
PATH Rehash
Parallel readdir across every PATH directory
History Writes
Daemon-side writes via IPC — prompt never waits, zero client SQLite handle
[0x03] AOP INTERCEPT
The first shell ever with aspect-oriented programming. Hook before,
after, or around any command or function
— at machine code speed, no fork. One primitive that replaces defer,
profile, memo, retry, and timeout.
Variables available in advice:
| Variable | Available | Description |
|---|---|---|
$INTERCEPT_NAME | all | Command name |
$INTERCEPT_ARGS | all | Arguments as space-separated string |
$INTERCEPT_CMD | all | Full command string |
$INTERCEPT_MS | after | Execution time in milliseconds (nanosecond source) |
$INTERCEPT_US | after | Execution time in microseconds |
$? | after | Exit status |
[0x04] RKYV ZERO-COPY CACHE LAYER
Cached bytecode lives in rkyv archives mmap'd by clients — zero-copy
deserialization, no allocator pressure on hot paths. The daemon owns all writes; clients
only mmap. The single queryable mirror is catalog.db (SQLite) for
dbview introspection — never touched on the hot path.
images/{hash}-{slug}.rkyv
Per-source-root rkyv shards. Compiled bytecode for autoloads, plugins, sourced scripts. Daemon writes; clients mmap zero-copy. Validates with rkyv's bytecheck on first access; format-versioned for migration safety.
index.rkyv
Top-level shard index: source-root path → shard hash + mtime. Daemon rewrites on changes; clients mmap to resolve which shard to load.
catalog.db (SQLite mirror)
Daemon-hydrated FTS5-indexed mirror of all rkyv contents. Used ONLY by
dbview and zcache introspection — never read on the
hot path. Clients have ZERO SQLite handles. The daemon's own FTS5 history
(history.db) sits next to it; the shell's user-facing history is
zshrs_history (flat zsh-extended-history-format text) plus a sibling
FTS5 index zshrs_history.db, both in $ZSHRS_HOME.
Browse caches without SQL:
[0x04b] FILE LAYOUT & BINARIES
One directory holds every zshrs file: $ZSHRS_HOME (defaults to
~/.zshrs/). All four binaries — zshrs, zshrs-daemon,
zshrs-recorder, zd — seed the directory and three default configs
on first run via CachePaths::ensure_default_configs. Idempotent — never
overwrites user edits. Auto-migrates legacy daemon.toml →
zshrs-daemon.toml and the legacy macOS history at
~/Library/Application Support/zshrs/history.db →
~/.zshrs/zshrs_history.db.
Three log files (one per binary)
| File | Owner | Level source |
|---|---|---|
zshrs.log | shell | [log] level in zshrs.toml (env $ZSHRS_LOG wins) |
zshrs-daemon.log | daemon | [log] level in zshrs-daemon.toml |
zshrs-recorder.log | recorder | [log] level in zshrs-recorder.toml |
Helper paths::is_zshrs_log_file matches all three for rotation, tail, and
clear. Default daemon startup emits 7 INFO lines; with level = "trace" an
additional ~5 startup diagnostics print (resolved env, pidlock, db sizes, ticker spawn,
schedule spawn, http listener address).
[shell] skip_configs = "auto"
When the daemon is up AND has a recorded zshrs canonical shard, the shell SKIPS
sourcing /etc/zshenv + ~/.{zshenv,zprofile,zshrc,zlogin} entirely
and rebuilds executor state from the rkyv shard. canonical_apply wires
alias/galias/salias/env/params/setopt/path/fpath/named_dirs/autoload_functions/zstyle/bindkey/compdef/zle widgets.
Inline functions are deferred until the recorder ships bytecode in the shard.
[0x04c] RECORDER (zshrs-recorder)
Separate binary — not a flag on zshrs. Sources the full zsh login
chain in order, skipping any missing file silently:
-f PATH overrides the chain to source one file. End-of-run ships a
recorder_ingest IPC bundle to the daemon, which folds it into the canonical
shard for the matching source root.
[0x04d] zd — TWO SURFACES
| Surface | Transport | Latency | Use case |
|---|---|---|---|
zd binary | HTTP via ureq to [http].listen | 2.64 ms (fork+exec) | bash, fish, CI, anything outside zshrs |
zd builtin (in-process) | local Unix socket via Client | 0.48 ms (5.5× faster) | inside zshrs — same arg surface |
Default HTTP listener: 127.0.0.1:7733, seeded into
zshrs-daemon.toml on first run so zd health works out of the
box. Default token comes from $DAEMON_TOKEN. Loopback bind requires no
token; non-loopback bind refuses to start until [http.tokens] is populated.
GET /openapi (alias /openapi.json) returns an OpenAPI 3.1
document auto-derived from OP_NAMES — 108 paths today (4 meta + 101 ops
+ 3 streams), with ErrPayload in components and bearerAuth
declared only when tokens are configured.
zd subcommands
zd export aliases pdf > out.pdf produces a real PDF — payload is
streamed direct for sh / csv / json / yaml / text / pdf. --json
opt-in restores the JSON envelope.
[0x04e] NEW z* BUILTINS
| Builtin | Description |
|---|---|
zlock try NAME | Cross-process named lock — non-blocking try |
zlock acquire NAME [--timeout S] | Poll-wait acquire with optional timeout |
zlock release NAME TOKEN | Release by token |
zlock list | List all held locks |
zpublish TOPIC / TOPIC DATA / TOPIC --json '{...}' | Producer side of the pub/sub bus (consumer is zsubscribe) |
zwhere SUBSYS NAME | Query daemon canonical state for "where did this alias / function / zstyle come from" with file:line attribution |
Full builtin family: zcache, zls, zid,
zping, ztag, zuntag, zsend,
znotify, zsubscribe, zunsubscribe,
zjob, zsync, zask, zhistory,
zsource, zcomplete, zsuggest,
zcmd-result, zlog, zwhere, zd,
zlock, zpublish.
zjob — bidirectional ptmx attach
With --pty the daemon calls nix::pty::openpty() and the
child runs with the slave on 0/1/2 via pre_exec dup2; the
master fd lives in JobMeta. Two new IPC ops drive it:
job_input {id, bytes_b64}— base64-decode, write to masterjob_resize {id, rows, cols}—TIOCSWINSZfor SIGWINCH propagation
On zjob attach against a pty job: termios cfmakeraw on
stdin, a stdin reader thread batches keystrokes and fires job_input, output
is broadcast as job:N.stdout events with bytes_b64 payload
(decoded back to the user's tty), and SIGWINCH is forwarded as
job_resize. Ctrl-] is the detach key. Termios is restored on
exit via a Drop guard. Use case: background jobs that block on stdin (deploy scripts
asking y/n, REPLs in background) — submit with --pty, attach later, type,
detach, attach again.
zsync up --all
Now wired (was a stub). The shell-side enumerator
(src/extensions/overlay_snapshot.rs) snapshots every overlay table and ships
push_canonical per subsystem. Covers
alias/galias/salias, setopt, params (vars+arrays+assoc unioned), env, path, manpath,
fpath, named_dir, compdef, zstyle. Skipped: function (needs source-text
round-trip), bindkey (lives in ZleManager),
zmodload (no canonical "currently loaded" list).
[0x05] BYTECODE CACHING
Every autoload function in fpath gets compiled to fusevm bytecodes during compinit.
Compiled chunks are serialized via rkyv into per-source-root shards and written
by the daemon. Subsequent loads mmap the archive zero-copy, validate via bytecheck,
and dispatch directly — no lex, no parse, no compile, no allocator hit.
[0x06] FUSEVM BYTECODE TARGET
100% lowered. Every shell construct compiles to fusevm
bytecodes — a language-agnostic VM with fused superinstructions and Cranelift JIT.
The same VM that powers stryke (which beats LuaJIT on benchmarks). No tree-walking
interpreter remains. Hot bytecodes compile to native x86-64 machine code.
Shell constructs already lowered to fusevm bytecodes:
Arithmetic
$(( )) — full precedence, ternary, assignment, hex/octal, bitwise
Loops
for, for(()), while, until, repeat
Conditionals
if/elif/else, case, [[ ]] with all file tests and comparisons
Commands
Exec, ExecBg, pipelines, redirects, here-docs, here-strings
Functions
Definition, Call/Return, PushFrame/PopFrame
Fused Ops
AccumSumLoop, SlotIncLtIntJumpBack, PreIncSlotVoid — single-dispatch loop execution
[0x06b] COMPLETION CODEPATH — ZSH vs ZSHRS
Both shells share the same entry — zshrs ports callcompfunc faithfully — then split where each completion function actually runs. zsh re-interprets the whole framework as shell on every tab and walks $fpath to find the leaf; zshrs runs the framework as native Rust and resolves the leaf from an index. This is a structural codepath trace, not a benchmark — the claim is about which work exists on each path.
Same flow, expanded to every frame — the shapes are the point: zsh descends a deep interpreted stack; zshrs forks once at dispatch_compsys into native fn-pointers and a fusevm-Chunk leaf.
This is every autoloaded function, not just completers — in zsh that is the majority of the function surface (all of compsys, ZLE widgets, prompt themes, vcs_info, hooks). On first call zsh must find it in $fpath then lex → parse → compile → interpret it; zshrs mmaps the already-compiled fusevm bytecode straight from the rkyv shard — the whole compile pipeline is skipped.
| Stage | zsh — every <tab> | zshrs |
|---|---|---|
| Spine | interpret ~1,517 shell lines (_main_complete → _dispatch) | native Rust fn pointer — compiled into the binary |
| Resolve leaf | scan $fpath — 51 dirs, stat per dir until hit | rkyv-mmap'd shard (~/.zshrs/*.rkyv) + .zwc digest, mmap — no fpath walk |
| Load + run leaf | autoload + parse on first tab, interpret specs | fusevm bytecode + JIT · .fjit disk cache, warm across launches |
| Emit | compadd (C builtin) | bin_compadd (native) |
Sources: callcompfunc compcore.c:544,:991 · body-runner fork compcore.rs:766–782 (native dispatch :776) · router router.rs:57–61 · leaf resolution exec.rs:811 (getfpfunc/try_dump_file) · JIT disk cache Cargo.toml:103. Figures from this machine's fpath depth and completion corpus.
[0x06c] HOT-LOOP EXECUTION — SIGIL SCAN vs JIT
Inside a loop, zsh re-does parameter expansion on every pass: prefork runs stringsubst/paramsubst, which scan each word character-by-character for sigils ($, ${...}, expansion flags) and re-expand, then the wordcode is walked again. The scan is O(tokens) work repeated ×N. zshrs resolves sigils to fusevm bytecode ops once at compile time; the hot loop is then JIT-compiled to native code (Cranelift, cached in .fjit), so each pass runs machine code with no re-scan.
| Per loop pass | zsh | zshrs |
|---|---|---|
| Sigil / expansion scan | re-scanned each pass (stringsubst / paramsubst) | resolved to ops once at compile |
| Dispatch | walk wordcode (prefork → exec) | fused loop ops, single-dispatch |
| Hot loop | interpreted every iteration | tracing JIT → native (Cranelift), .fjit cached |
| Cost per pass | O(tokens) | O(1) |
Sources: zsh per-exec expansion prefork Src/exec.c:2545, stringsubst/paramsubst Src/subst.c · zshrs tracing JIT src/fusevm_bridge.rs:1203–1212 (enable_tracing_jit), fused loop ops (AccumSumLoop, SlotIncLtIntJumpBack).
[0x07] EXCLUSIVE BUILTINS
Parallel Primitives (VM-executed, zero fork)
| Builtin | Description |
|---|---|
async / await | Ship work to pool, collect result |
pmap | Parallel map with ordered output — compiles to bytecode, runs on VM, zero forks |
pgrep | Parallel filter — compiles to bytecode, runs on VM, zero forks |
peach | Parallel for-each, unordered — compiles to bytecode, runs on VM, zero forks |
barrier | Run all commands in parallel, wait for all |
AOP / Debugging
| Builtin | Description |
|---|---|
intercept | AOP before/after/around advice on any command. Glob pattern matching. Nanosecond timing. |
intercept_proceed | Call original command from around advice. |
doctor | Full diagnostic: worker pool metrics, cache stats, bytecode coverage, startup health. |
dbview | Browse the daemon's catalog.db mirror without SQL. Tables: autoloads, comps, executables, history, plugins. Hot path uses rkyv mmap directly. |
profile | In-process command profiling. Nanosecond accuracy. No fork overhead in measurement. |
Unit Test Framework
Port of the strykelang test framework
(stryke test → zshrs --ztest). Worker-pool runner — one persistent
--ztest-worker subprocess per CPU, fork-on-receive per test file, JSON-over-pipe wire protocol,
per-test fd 2 capture so concurrent workers can’t tear each other’s lines.
Test discovery: test_* / t_* prefix × .zsh/.sh/.zshrs
suffix under t/ or tests/.
| Builtin | Description |
|---|---|
zassert_eq / zassert_ne | String equality / inequality |
zassert_ok / zassert_err / zassert_true / zassert_false | Truthiness (non-empty AND not "0" → truthy) |
zassert_gt / zassert_lt / zassert_ge / zassert_le | Numeric ordering |
zassert_match | Regex match (Rust regex syntax) |
zassert_contains | Substring containment |
zassert_near | Float approximate equality (epsilon) |
zassert_dies | Passes when given shell command exits non-zero |
ztest_skip | Mark current assertion skipped (yellow ↓) |
ztest_run / run_tests | Print summary, roll counters into totals |
zshrs --ztest [-j N] [-q] [paths…] | Worker-pool runner (default workers = num_cpus) |
zshrs --ztest-worker | Persistent worker subprocess (JSON over stdin/stdout) |
Coreutils (Anti-Fork)
| Builtin | Description | Speedup vs fork |
|---|---|---|
cat | Concatenate files — no fork | 2000-5000x |
head / tail | First/last N lines — no fork | 2000-5000x |
wc | Line/word/char count — no fork | 2000-5000x |
sort / uniq | Sort and dedupe — no fork | 2000-5000x |
find | Walk directories — no fork | 2000-5000x |
cut / tr / rev | Text manipulation — no fork | 2000-5000x |
seq / tee | Number sequences, copy stdin — no fork | 2000-5000x |
date | Current date/time — direct strftime | 3000-8000x |
sleep | Delay — std::thread::sleep | 2000-5000x |
mktemp | Create temp file/dir — no fork | 2000-5000x |
hostname / uname | System info — direct syscall | 3000-8000x |
id / whoami | User info — direct syscall | 3000-8000x |
touch / realpath | File ops — no fork | 2000-5000x |
basename / dirname | Path manipulation — no fork | 2000-5000x |
zgetattr / zsetattr | xattr ops — direct syscall | 2000-5000x |
[0x07b] SHELL LANGUAGE FEATURES
Every shell construct compiles to fusevm bytecode — no tree-walker dispatch lives in zshrs. The categories below are summaries; the full reference documents each entry with a runnable code example.
Control Flow
if, while, until, for, for ((;;)),
case, select, coproc, break, continue,
return, ;/&&/||/&.
Indexed Arrays
arr=(a b c), arr+=(d), ${arr[1]} (1-based),
${arr[-1]}, ${arr[@]} (argv splice), ${#arr[@]}.
Associative Arrays
typeset -A m, m[key]=val, ${m[key]},
${(k)m} (keys), ${(v)m} (values).
Parameter Expansion
${var:-x}, ${var:=x}, ${var:?msg},
${var:+x}, ${#var}, ${var:o:l}, ${var#pat},
${var/pat/repl}, ${var:u}/:l.
Zsh Flags
(L)/(U) case, (j: :) join,
(s. .) split, (f) newline-split, (o)/(O) sort,
(P) indirect, (@) force-array, (k)/(v),
(#). Stack: (jL), (s:,:U).
Redirects & Pipelines
> >> < <<EOF <<<
2>&1 &> &>> |
|& ! <(cmd) >(cmd).
Background & Async
cmd & (fork + setsid), $!, wait,
jobs/fg/bg; async/await
(worker pool, no fork).
Coprocesses
coproc { body } creates two pipes, forks, registers
$COPROC=[read_fd, write_fd]. Read from /dev/fd/${COPROC[1]},
write to /dev/fd/${COPROC[2]}.
Eval & Indirect Dispatch
eval 'echo $x' defers expansion correctly (single-quoted specials honored).
cmd=ls; $cmd routes through host intercepts.
Arithmetic
$((expr)), (( cond )), let; full integer expression
grammar compiled inline (no runtime parser).
Glob & Brace
*.rs, **/*.rs (parallel walk), {a,b,c},
{1..10}, glob qualifiers *(.x) *(N).
Tilde & Cmd-Sub
~, ~user, ~+/~-; $(cmd)
(in-process pipe-capture), backticks.
For the complete catalog — every supported builtin, keyword, parameter-expansion form, ZshFlag, AOP primitive, parallel primitive, and anti-fork coreutils replacement with a code example for each — see the FULL REFERENCE.
[0x08] INSTALL
[0x09] DIAGNOSTICS
[0x09c] GRAMMAR & SYNTAX — CANONICAL TABLES
Every reserved keyword, builtin, setopt option, special variable,
parameter flag, glob qualifier, operator, history expansion, and word modifier
recognized by zshrs. Sourced directly from upstream zsh C tables
(Src/hashtable.c::reswds[], Src/builtin.c::builtins[],
Src/Modules/*.c, Src/options.c::optns[],
Src/params.c::special_params[]) plus the zshrs extension
builtins in daemon/builtins.rs + src/extensions/.
Single source of truth lives at data/grammar/canonical.json;
regenerate this section via python3 scripts/gen_grammar_docs.py.
Generated 2026-05-31 from data/grammar/canonical.json.
Reserved keywords (50)
Control flow (20)
[[
]]
always
case
do
done
elif
else
end
esac
fi
for
foreach
if
in
repeat
select
then
until
while
Declaration (10)
declare
export
float
integer
let
local
readonly
set
shift
typeset
Function (1)
function
Grouping (2)
{
}
I/O / source (5)
.
eval
exec
source
trap
Loop control (5)
break
continue
exit
logout
return
Modifier (precommand) (6)
builtin
command
coproc
nocorrect
noglob
time
Operator-like (1)
!
Builtins (157)
POSIX / zsh core (74)
.
:
[
alias
autoload
bg
break
bye
cd
chdir
continue
declare
dirs
disable
disown
echo
emulate
enable
eval
exit
export
false
fc
fg
float
functions
getln
getopts
hash
hashinfo
history
integer
jobs
kill
let
local
logout
mem
patdebug
popd
print
printf
pushd
pushln
pwd
r
read
readonly
rehash
return
set
setopt
shift
source
suspend
test
times
trap
true
ttyctl
type
typeset
umask
unalias
unfunction
unhash
unset
unsetopt
wait
whence
where
which
zcompile
zmodload
zsh modules (59)
cap
chgrp
chmod
chown
clone
echotc
echoti
example
getcap
ln
local
log
mkdir
mv
nameref
pcre_compile
pcre_match
pcre_study
private
rm
rmdir
setcap
stat
strftime
sync
syserror
sysopen
sysread
sysseek
syswrite
zcurses
zdelattr
zf_chgrp
zf_chmod
zf_chown
zf_ln
zf_mkdir
zf_mv
zf_rm
zf_rmdir
zf_sync
zformat
zftp
zgdbmpath
zgetattr
zlistattr
zparseopts
zprof
zpty
zregexparse
zselect
zsetattr
zsocket
zstat
zstyle
zsystem
ztcp
ztie
zuntie
zshrs extensions (24)
zask
zcache
zcompdump
zcomplete
zd
zhistory
zid
zjob
zlock
zlog
zls
znotify
zping
zpublish
zsend
zsource
zsubscribe
zsuggest
zsync
ztag
zunsubscribe
zuntag
zwc
zwhere
setopt options (196)
ALIASES
ALIASFUNCDEF
ALLEXPORT
ALWAYSLASTPROMPT
ALWAYSTOEND
APPENDCREATE
APPENDHISTORY
AUTOCD
AUTOCONTINUE
AUTOLIST
AUTOMENU
AUTONAMEDIRS
AUTOPARAMKEYS
AUTOPARAMSLASH
AUTOPUSHD
AUTOREMOVESLASH
AUTORESUME
BADPATTERN
BANGHIST
BAREGLOBQUAL
BASHAUTOLIST
BASHREMATCH
BEEP
BGNICE
BRACECCL
BRACEEXPAND
BSDECHO
CASEGLOB
CASEMATCH
CASEPATHS
CBASES
CDABLEVARS
CDSILENT
CHASEDOTS
CHASELINKS
CHECKJOBS
CHECKRUNNINGJOBS
CLOBBER
CLOBBEREMPTY
COMBININGCHARS
COMPLETEALIASES
COMPLETEINWORD
CONTINUEONERROR
CORRECT
CORRECTALL
CPRECEDENCES
CSHJUNKIEHISTORY
CSHJUNKIELOOPS
CSHJUNKIEQUOTES
CSHNULLCMD
CSHNULLGLOB
DEBUGBEFORECMD
DOTGLOB
DVORAK
EMACS
EQUALS
ERREXIT
ERRRETURN
EVALLINENO
EXEC
EXTENDEDGLOB
EXTENDEDHISTORY
FLOWCONTROL
FORCEFLOAT
FUNCTIONARGZERO
GLOB
GLOBALEXPORT
GLOBALRCS
GLOBASSIGN
GLOBCOMPLETE
GLOBDOTS
GLOBSTARSHORT
GLOBSUBST
HASHALL
HASHCMDS
HASHDIRS
HASHEXECUTABLESONLY
HASHLISTALL
HISTALLOWCLOBBER
HISTAPPEND
HISTBEEP
HISTEXPAND
HISTEXPIREDUPSFIRST
HISTFCNTLLOCK
HISTFINDNODUPS
HISTIGNOREALLDUPS
HISTIGNOREDUPS
HISTIGNORESPACE
HISTLEXWORDS
HISTNOFUNCTIONS
HISTNOSTORE
HISTREDUCEBLANKS
HISTSAVEBYCOPY
HISTSAVENODUPS
HISTSUBSTPATTERN
HISTVERIFY
HUP
IGNOREBRACES
IGNORECLOSEBRACES
IGNOREEOF
INCAPPENDHISTORY
INCAPPENDHISTORYTIME
INTERACTIVE
INTERACTIVECOMMENTS
KSHARRAYS
KSHAUTOLOAD
KSHGLOB
KSHOPTIONPRINT
KSHTYPESET
KSHZEROSUBSCRIPT
LISTAMBIGUOUS
LISTBEEP
LISTPACKED
LISTROWSFIRST
LISTTYPES
LOCALLOOPS
LOCALOPTIONS
LOCALPATTERNS
LOCALTRAPS
LOG
LOGIN
LONGLISTJOBS
MAGICEQUALSUBST
MAILWARN
MAILWARNING
MARKDIRS
MENUCOMPLETE
MONITOR
MULTIBYTE
MULTIFUNCDEF
MULTIOS
NOMATCH
NOTIFY
NULLGLOB
NUMERICGLOBSORT
OCTALZEROES
ONECMD
OVERSTRIKE
PATHDIRS
PATHSCRIPT
PHYSICAL
PIPEFAIL
POSIXALIASES
POSIXARGZERO
POSIXBUILTINS
POSIXCD
POSIXIDENTIFIERS
POSIXJOBS
POSIXSTRINGS
POSIXTRAPS
PRINTEIGHTBIT
PRINTEXITVALUE
PRIVILEGED
PROMPTBANG
PROMPTCR
PROMPTPERCENT
PROMPTSP
PROMPTSUBST
PROMPTVARS
PUSHDIGNOREDUPS
PUSHDMINUS
PUSHDSILENT
PUSHDTOHOME
RCEXPANDPARAM
RCQUOTES
RCS
RECEXACT
REMATCHPCRE
RMSTARSILENT
RMSTARWAIT
SHAREHISTORY
SHFILEEXPANSION
SHGLOB
SHINSTDIN
SHNULLCMD
SHOPTIONLETTERS
SHORTLOOPS
SHORTREPEAT
SHWORDSPLIT
SINGLECOMMAND
SINGLELINEZLE
SOURCETRACE
STDIN
SUNKEYBOARDHACK
TRACKALL
TRANSIENTRPROMPT
TRAPSASYNC
TYPESETSILENT
TYPESETTOUNSET
UNSET
VERBOSE
VI
WARNCREATEGLOBAL
WARNNESTEDVAR
XTRACE
ZLE
Special variables (88)
$!
$#
$$
$*
$-
$0
$?
$@
$ARGC
$CDPATH
$COLUMNS
$EGID
$ERRNO
$EUID
$FIGNORE
$FPATH
$FUNCNEST
$GID
$HISTCHARS
$HISTCMD
$HISTSIZE
$HOME
$IFS
$KEYBOARD_HACK
$LANG
$LC_ALL
$LC_COLLATE
$LC_CTYPE
$LC_MESSAGES
$LC_NUMERIC
$LC_TIME
$LINENO
$LINES
$MAILPATH
$MANPATH
$MODULE_PATH
$NULLCMD
$OPTARG
$OPTIND
$PATH
$POSTEDIT
$PPID
$PROMPT
$PROMPT2
$PROMPT3
$PROMPT4
$PS1
$PS2
$PS3
$PS4
$PSVAR
$RANDOM
$READNULLCMD
$RPROMPT
$RPROMPT2
$RPS1
$RPS2
$SAVEHIST
$SECONDS
$SHLVL
$SPROMPT
$TERM
$TERMINFO
$TERMINFO_DIRS
$TRY_BLOCK_ERROR
$TRY_BLOCK_INTERRUPT
$TTYIDLE
$UID
$USERNAME
$WORDCHARS
$ZLE_RPROMPT_INDENT
$ZSH_EVAL_CONTEXT
$ZSH_SUBSHELL
$_
$argv
$cdpath
$fignore
$fpath
$histchars
$mailpath
$manpath
$module_path
$path
$pipestatus
$prompt
$psvar
$status
$zsh_eval_context
Parameter expansion flags (38)
| Flag | Meaning |
|---|---|
${(@)var} | array-context retain $@ semantics; quoting-preserving even in scalar context |
${(A)var} | create as an array |
${(a)var} | sort by array index |
${(c)var} | count words in a parameter (e.g., scalar split count) |
${(C)var} | capitalize words |
${(D)var} | treat as DIRECTORY name (apply directory substitution like ~/...) |
${(e)var} | perform parameter expansion / arithmetic / etc. on the result |
${(f)var} | split result at newlines |
${(F)var} | join array elements with newlines |
${(g)var} | process escape sequences like print does (g:o: process octals, g:c: process \c) |
${(i)var} | case-insensitive sort |
${(j)var} | join array with separator: ${(j:sep:)arr} |
${(k)var} | for assoc arrays: keys ${(k)hash} |
${(K)var} | subscript flags: use keys |
${(L)var} | lowercase |
${(M)var} | match: use longest match (also for case-insensitivity in sort) |
${(n)var} | numeric sort |
${(o)var} | sort ascending |
${(O)var} | sort descending |
${(p)var} | interpret embedded escape sequences in j/s separator |
${(P)var} | treat value as parameter name → indirect (P) |
${(q)var} | quote the result (q-/q+/qq/qqq variants for shell-quote levels) |
${(Q)var} | remove quoting |
${(r)var} | right-justify within field width: ${(r:N::pad:)var} |
${(l)var} | left-justify within field width: ${(l:N::pad:)var} |
${(s)var} | split at separator: ${(s:sep:)var} |
${(S)var} | subscript: search subscript ranges |
${(t)var} | test parameter type |
${(u)var} | unique (dedupe array) |
${(U)var} | uppercase |
${(v)var} | for assoc arrays: values ${(v)hash} |
${(V)var} | make invisible / control chars visible |
${(w)var} | split into words |
${(W)var} | split into words (alternate) |
${(z)var} | split as the shell would (z-tokens) |
${(#)var} | expand result as arithmetic; numeric value |
${(%)var} | expand prompt percent escapes in result |
${(~)var} | treat values as patterns (e.g., for /pat/repl) |
Glob qualifiers (44)
| Qual | Meaning |
|---|---|
*(/) | directories only |
*(.) | regular files only |
*(@) | symbolic links only |
*(=) | sockets only |
*(p) | named pipes (FIFOs) only |
*(*) | executable plain files only |
*(%) | device special files only |
*(%b) | block special files |
*(%c) | character special files |
*(r) | owner-readable |
*(w) | owner-writable |
*(x) | owner-executable |
*(A) | group-readable |
*(I) | group-writable |
*(E) | group-executable |
*(R) | world-readable |
*(W) | world-writable |
*(X) | world-executable |
*(s) | setuid (S_ISUID) |
*(S) | setgid (S_ISGID) |
*(t) | sticky bit |
*(d N) | device number N |
*(l[+-=]N) | exactly / less-than / greater-than N hard links |
*(U) | owned by EUID |
*(G) | owned by EGID |
*(u N) | owned by uid N |
*(g N) | group gid N |
*(f spec) | permission mask: f:o+w: e.g. |
*(L [+-=] N) | size: blocks / k / m / p suffix (Lk / Lm / Lp) |
*(a [Mwhms] [+-=] N) | access time |
*(m [Mwhms] [+-=] N) | modify time |
*(c [Mwhms] [+-=] N) | ctime |
*(o [name|size|links|mtime|atime|ctime]) | sort ascending |
*(O [name|size|links|mtime|atime|ctime]) | sort descending |
*([N,M]) | select range of matches |
*(e:str:) | external test: each match passed to expression |
*(+func) | external test: each match passed to function |
*(N) | nullglob: silently drop unmatched pattern |
*(D) | include dotfiles |
*(Y N) | limit to N results |
*(M) | include directory names as if trailing slash |
*(:mod) | apply history modifier (e.g., :t :h :r :e) |
*(^) | negate the qualifier list |
*(,) | OR-combine qualifier groups |
Operators / redirections / substitution forms (55)
| Symbol | Kind | Meaning |
|---|---|---|
| | pipeline | Pipeline. stdout of LHS → stdin of RHS. |
|& | pipeline | Pipeline with stderr merged (= |2>&1). |
&& | list | Logical AND: run RHS only if LHS exit==0. |
|| | list | Logical OR: run RHS only if LHS exit!=0. |
; | list | Sequence: run RHS after LHS regardless of status. |
& | list | Background: run LHS async; sets $!. |
;; | case | End case arm. |
;;& | case | Fall through and test next case pattern. |
;| | case | Fall through without test. |
! | neg | Negate exit status (reserved word). |
> | redirect | Stdout redirect (overwrite). |
>> | redirect | Stdout append. |
< | redirect | Stdin redirect. |
<< | redirect | Heredoc; body terminated by marker. |
<<- | redirect | Heredoc, strip leading tabs from body. |
<<< | redirect | Here-string; literal text as stdin. |
>| | redirect | Stdout force-overwrite (bypass NO_CLOBBER). |
>! | redirect | Same as >|; force-overwrite. |
&> | redirect | Redirect both stdout and stderr (bash-compat). |
&>> | redirect | Append both stdout and stderr. |
2>&1 | redirect | Duplicate fd2 to fd1 (stderr → stdout). |
>&- | redirect | Close fd. |
<> | redirect | Open for read+write. |
<( | procsub | Process substitution: <(cmd) is a path readable from cmd's stdout. |
>( | procsub | Process substitution: >(cmd) is a path writable into cmd's stdin. |
=( | procsub | Zsh-only =(cmd): tempfile capture. |
$( | subst | Command substitution: $(cmd) captures cmd's stdout. |
${ | subst | Parameter expansion: ${var}. |
$(( | subst | Arithmetic expansion: $((expr)). |
(( | arith | Arithmetic command. ((expr)) exits 0 iff expr != 0. |
)) | arith | Close arithmetic command. |
[[ | cond | Open conditional command: [[ expr ]]. |
]] | cond | Close conditional command. |
= | assign | Assignment. Also: equality in [[ ]]. |
+= | assign | Append assignment (scalar concat / array push). |
-= | assign | Numeric subtract-assign (in (( ))). |
:= | assign | ${var:=default}: assign default if unset/empty. |
?= | assign | (arith) ternary. |
== | compare | Equality in (( )) / [[ ]]. |
!= | compare | Inequality. |
=~ | compare | Regex match in [[ ]] (POSIX ERE / PCRE depending on opts). |
* | glob | Glob: match any sequence (including empty). |
** | glob | Recursive glob (matches dir/subdir/.../ levels). |
? | glob | Glob: match one character. |
~ | tilde | Tilde expansion: ~ → $HOME, ~user, ~+ / ~- / ~N for dirstack. |
{a,b,c} | brace | Brace expansion: comma-separated list. |
{1..10} | brace | Brace expansion: numeric range. |
{a..z} | brace | Brace expansion: character range. |
${~var} | expansion | Treat result of var as pattern. |
${^var} | expansion | Array element rcexpansion. |
${=var} | expansion | Word-split on IFS. |
$'…' | string | ANSI-C quoted string: \n \t \xNN etc. |
$"…" | string | Locale-translated string. |
`…` | subst | Backtick command substitution (legacy form of $()). |
@{} | extension | Zshrs @-prefix: dispatch to stryke embedded scripting. |
History expansions (10)
| Form | Meaning |
|---|---|
!! | previous command |
!N | command N in history |
!-N | command N back |
!?str | most recent containing str |
!str | most recent starting with str |
!$ | last word of previous command |
!^ | first arg of previous command |
!* | all args of previous command |
!:N | Nth word of previous command |
^old^new | quick substitute in previous command |
Word modifiers (16)
| Modifier | Meaning |
|---|---|
:h | head: dirname of path |
:t | tail: basename of path |
:r | root: strip extension |
:e | extension: keep only extension |
:l | lowercase |
:u | uppercase |
:q | quote for shell re-input |
:Q | remove quoting |
:s/old/new/ | substitute first match |
:gs/old/new/ | global substitute |
:a | absolutize (resolve relative path) |
:A | absolutize and resolve symlinks |
:P | physical resolved path |
:x | split into words on whitespace |
:w | select words |
:F | follow symlinks (in conjunction with above) |
[0xFF] THE PHILOSOPHY
Shells haven't fundamentally improved since the 1990s. bash is a GNU rewrite of the Bourne shell from 1979. zsh added features but kept the fork-based C architecture. fish focused on UX and abandoned POSIX. nushell reinvented the data model but lost compatibility.
zshrs takes a different approach: keep everything that makes zsh powerful —
glob qualifiers, parameter expansion flags, the completion system, ZLE, zstyle, modules —
and replace the runtime with modern systems engineering. Rust instead of C. Thread pool
instead of fork. rkyv mmap'd zero-copy archives instead of flat files. Bytecode VM
instead of tree-walker. AOP instead of monkey-patching.
The result is the first shell that gets faster as you add more plugins,
because the plugin cache means each plugin is only parsed once. The first shell where
**/*.rs scales with your CPU count. The first shell where you can
intercept any command with nanosecond-accurate timing and zero overhead.
The result is the first shell where every command — interactive or scripted —
compiles to bytecodes and executes on a VM with fused superinstructions. The first
shell where autoload functions load from pre-compiled bytecodes in microseconds. The first
shell where source ~/.zshrc can skip the lexer, parser, and compiler entirely
because the bytecodes are mmap'd zero-copy from rkyv archives the daemon
already validated.
Since the Bourne shell at Bell Labs in 1970, through csh, ksh, bash, zsh and fish, no Unix shell has emitted native machine code. Nushell reached bytecode in 2024 (IR in 0.96.0, default in 0.98.0) and still interprets it, per parse, with no on-disk cache. zshrs is the first to JIT to native code and the first to persist its bytecode across processes. Shell scripts at machine code speed. Achieved in alpha.
THE FIRST JIT-COMPILED UNIX SHELL. THE MOST POWERFUL SHELL EVER CREATED.