// ZSHRS — FULL REFERENCE

zshrs v0.9.4 · 14 chapters · every supported builtin, keyword, expansion form & AOP primitive — pinned by 398 regression tests (227 corpus · 158 dispatch · 8 absence · 5 unit)

Hub GitHub
// Color scheme

>_SHELL REFERENCE

Every builtin, keyword, parameter-expansion form, ZshFlag, array shape, AOP primitive, parallel primitive, and anti-fork coreutils replacement that zshrs ships. Each entry shows what it does and a runnable example. Coverage gate: 344 construct-corpus tests in tests/zsh_construct_corpus.rs exercise every category below; 817 tests total across all suites pass on the new (default) pipeline. Jump via the chapter index, or Ctrl+F for a specific name.

Pipeline Architecture

Two execution pipelines exist; the new one is the default as of session 2026-04-27:

  • New (default): ZshLexer (port of zsh/Src/lex.c) → ZshParser (port of zsh/Src/parse.c) → ZshCompiler (src/compile_zsh.rs) → fusevm bytecode. Lex + parse are direct C-source ports; the bytecode compiler is the only original work. Faster, more faithful to zsh's grammar.
  • Old (escape hatch): set ZSHRS_OLD_PIPELINE=1 to route through the legacy hand-rolled ShellLexer/ShellParser/ShellCompiler. Slated for deletion once corpus parity is proven across all dogfood scenarios.

A residual bridge inside compile_word_str still re-parses param-modifier forms (${x:-default}, ${x#prefix}, ${x/pat/rep}) through the legacy ShellParser via untokenize_preserve_quotes round-trip. Each iteration of the migration replaces one runtime-fallback call site with native ops; tracked as Phase 1 / G8 in ROADMAP.md.

Coverage Gate

Each construct in this reference is pinned by at least one assertion in tests/zsh_construct_corpus.rs. The corpus runs the real zshrs -f -c <script> binary and asserts exact stdout + exit status. Categories covered:

  • Simple commands, multi-word arguments, assignments (chained, empty, quoted, single-quoted)
  • List operators: ;, &&, ||, &, short-circuit chains
  • Control flow: if/elif/else, while, until, for in, for ((;;)), case (literal/glob/alternation), select, coproc
  • break / continue / return with status
  • Functions: name() {}, function keyword, local scope, $1…$# + "$@" splice
  • Pipelines: 2/3-stage, function-in-first-stage, negation
  • Redirects: >, >>, <, <<EOF, <<<str, 2>&1, block redir, <&fd with var-expanded fd, close-fd
  • Quoting: single, double, $'…' ANSI-C, backtick, escaped \$ in dq
  • Parameter expansion: every modifier from zshexpn(1) — default, assign, error, alternate, length, substring, strip prefix/suffix (short/long, with glob), replace first/all, upper/lower postfix
  • ZshFlags: L, U, j:s:, s:s:, f, o, O, P, @, k, v, #, q (level 1-4), q+, q-, q*, q!, g, n, i, t, %, e, p, A, ~, plus stacking
  • Special parameters: $?, $$, $@, $*, $#, $N, $!, $_, $-
  • Command substitution: $(…), backticks, nested, in array literal (with word-split)
  • Arithmetic: $((…)), ((…)), let — addition/precedence/paren/div/mod/pow/bitwise/shift/inc/dec/compare/logical/ternary, with var sync to executor.variables
  • Tilde, brace ranges (numeric/letter/zero-pad), brace alternation (nested + prefix/suffix), brace in for-loop
  • Glob: *, ?, […], qualifiers ((.), (/), (@), (N), (x/r/w), sort modifiers o/O/oL/OL/om/Om)
  • Process substitution: <(cmd), >(cmd)
  • Heredoc + herestring
  • Indexed arrays: literal, append +=(), index 1-based + negative, splice ${arr[@]}, length, iteration, empty, quoted-element preservation
  • Associative arrays: typeset -A, m[k]=v, m[k]+=tail, lookup, keys, values, missing-key empty
  • [[ … ]]: ==/!=/</>, glob, -z/-n, numeric -eq/-lt/-le/-gt/-ge, file tests -e/-f/-d/-r, logical &&/||/!, regex =~ with anchors + capture groups
  • POSIX builtins: cd, pwd, echo, print, printf, read (single/multi-var/array), eval, exec, set, shift, unset, export, alias/unalias, type, true/false/:, test/[
  • Coreutils builtins (anti-fork): seq, basename, dirname, tr (with range expansion), cat, head/tail, wc, sort, uniq, find, cut, rev, tee, sleep, mktemp, whoami, id, hostname, uname, date, touch, realpath
  • Background &, async/await, coproc round-trip via /dev/fd/N
  • Eval with deferred expansion (single-quoted args), multi-command eval
  • Bang (!) — literal in non-interactive (with surrounding chars), command negation when standalone
  • Aliases — regular + with args
  • Subshells (…) with full state isolation (variables/arrays/assoc/positional/cwd restored)
  • (exit N) in a subshell exits the subshell only, not the parent; status propagates through SubshellEnd
  • Brace groups {…} (no isolation, command-scope only) — and compound forms with trailing redirects: { … } 2>&1, (…) >file, if …; fi >file via ZshCommand::Redirected
  • Anonymous functions () { body } args… — auto-call form executes once with the trailing words as $1…$N
  • Trap EXIT (fires on script exit through both pipelines)
  • Repeat N cmd
  • Case fall-through ;& (run next arm without re-testing) and test-next ;| (re-test next arm's pattern)
  • |& pipe with stderr merge (cmd1 |& cmd2cmd1 2>&1 | cmd2)
  • Heredoc variants: <<EOF (var-expand), <<-EOF (strip leading tabs), <<'EOF' / <<"EOF" (quoted-terminator → verbatim, no expansion)
  • Special parameters: $?, $$, $@, $*, $#, $N, $!, $_, $-, $RANDOM, $SECONDS, $EPOCHSECONDS, $LINENO, $REPLY, PIPESTATUS / pipestatus
  • [[ -v var ]] existence test (scalar/array/assoc/env)
  • ${a[$i]} variable subscript ($i, ${expr}, $((arith)) all resolve before subscript parsing)
  • ${v: -3} negative substring offset (space disambiguates from ${v:-default})
  • ${var:=default}, ${var:?msg}, ${var:+alt}, ${v/#prefix/repl} / ${v/%suffix/repl} (anchor variants of replace)
  • Arithmetic with $NAME / ${NAME} / $N inside ((…)), $((…)), for ((…)) — pre-loaded via BUILTIN_GET_VAR
  • set -e, getopts, read into multiple vars / default $REPLY
  • printf format-string repetition (POSIX: re-applies format while args remain)

Out-of-scope (separate Phase work): zsh modules (zsh/curses, zsh/net/socket, zsh/zftp, zsh/zselect, zsh/sched), interactive ZLE widget firing on Tab + bindkey'd user widgets, async compinit fpath pre-warm, full setopt option matrix (~200 options × 2 toggles), some long-tail glob qualifiers (L+N size, mh-N mtime, om[1,N] "newest N"), and the dogfood gate (zpwr load + .zshrc as login shell for 14 days). The tests/ztst_runner.rs wrapper has been removed from CI — it was a fake-pass per-block runner; replacement is the PTY harness designed in docs/ZTST_PTY_HARNESS.md, not yet built.

Chapters

Control Flow

14 topics · compiled to fusevm bytecode with deferred jump patches; no tree-walker dispatch. Includes anonymous-fn auto-call and case fall-through (;&) / test-next (;|).

# if … then … fi

Standard POSIX conditional. elif branches and an optional else are supported. Status semantics: a Status(0) is truthy in zshrs, so if cmd; then …; fi runs the then arm when cmd exits 0. The compile path emits JumpIfFalse at the end of each conditional probe.

if [[ -d "$dir" ]]; then
  cd "$dir"
elif [[ -f "$dir" ]]; then
  echo "regular file"
else
  echo "no such path"
fi

# while … do … done

Loops while the condition exits 0. Loop-body status is preserved across iterations via a dedicated slot — the loop's exit status is the body's last-command status (or 0 if the body never ran), matching POSIX.

i=0
while (( i < 5 )); do
  echo "iter $i"
  (( i++ ))
done

# until … do … done

Inverse of while — loops while the condition is non-zero. Compiles to the same bytecode shape with the condition jump inverted.

until ping -c1 -W1 example.com >/dev/null; do
  sleep 1
done
echo "online"

# for var in words; do … done

Iterates var over the word list. Words are compiled at runtime${arr[@]} splices into N iterations via BUILTIN_ARRAY_FLATTEN. The for-loop preserves break/continue patches as deferred jump sites so nested loops work correctly.

for f in *.rs; do
  echo "compiling $f"
done

arr=(alpha beta gamma)
for x in ${arr[@]}; do
  echo "got $x"
done

# for ((init; cond; step)); do … done

C-style arithmetic for loop. The arithmetic expressions compile through the inline arithmetic compiler (no $((…)) wrapping needed inside the parens).

for ((i = 0; i < 10; i++)); do
  echo "i=$i"
done

# case … esac

Pattern-matched dispatch. Patterns are compiled via compile_case_pattern which preserves $-expansion but does not glob-expand the pattern itself (otherwise case x in *) … would expand * to the cwd listing). Match check uses Op::StrMatch (host-routed glob match).

case "$1" in
  start) echo starting ;;
  stop)  echo stopping ;;
  *)     echo "usage: $0 {start|stop}"; exit 1 ;;
esac

# select var in words; do … done

Interactive numbered-menu loop. Prints 1) word1\n2) word2\n… to stderr, prompts via $PROMPT3 (default ?# ), reads stdin, sets var to the chosen word, runs the body. $REPLY contains the raw input. EOF on stdin exits the loop. The real break keyword exits early via the cross-VM LoopSignal mechanism; the legacy BREAK_SELECT=1 sentinel still works for back-compat.

select choice in build test deploy quit; do
  case $choice in
    build)  cargo build ;;
    test)   cargo test ;;
    deploy) ./deploy.sh ;;
    quit)   break ;;
  esac
done

# () { body } args…

Anonymous function. Defined and immediately invoked with the trailing words as $1…$N. Parser routes Inoutpar (the () token) to parse_anon_funcdef, which generates a unique _zshrs_anon_N name; compile_funcdef registers and calls when auto_call_args is set. () with no body falls back to an empty subshell (POSIX no-op).

() { echo "hi $1, $2 args"; } world 1
# → hi world, 1 args

# case;& fall-through, ;| test-next

Per-arm separators after a case body. ;; exits (default), ;& falls through to the next arm without re-testing its pattern, ;| falls through and does re-test. compile_case tracks a pending_fall patch site so ;& emits a forward jump that the next arm's body-start patches.

case "$x" in
  a) print A ;&     # falls into b's body
  b) print B ;;
  *) print other ;;
esac

# coproc [name] { body }

Forks the body with bidirectional pipes. Two pipes are created (parent→child stdin, child→parent stdout); child setsids and runs the body, parent stores [read_fd, write_fd] in $COPROC (or the given name as an array). Read child output via /dev/fd/${COPROC[1]}, write to its stdin via /dev/fd/${COPROC[2]}. Job-table integration is Phase G6.

coproc { while read line; do echo "ECHO: $line"; done }
echo hello > /dev/fd/${COPROC[2]}
read reply < /dev/fd/${COPROC[1]}
echo "$reply"   # ECHO: hello

# break

Exits the innermost for/while/until. Compiled as a deferred forward Jump(0) patched to land just past the loop's exit point. Inside select use BREAK_SELECT=1 instead until cross-construct loop control lands in Phase G6.

for f in *.log; do
  [[ -s $f ]] || break    # stop on first empty
  process "$f"
done

# continue

Skip to the next iteration. Same patch mechanism as break — patches into the loop's continue target which is the iteration step (e.g. PreIncSlotVoid in the for-loop).

for n in 1 2 3 4 5; do
  (( n % 2 == 0 )) || continue
  echo "even: $n"
done

# return [n]

Return from a function with the given status (default last-status). Compile emits a forward Op::Jump(0) patched to past the chunk's end; standalone-chunk VMs interpret this as halt. Op::Return alone restarts the body from ip=0, which is wrong for top-level scripts.

greet() {
  [[ -z "$1" ]] && return 1
  echo "hello, $1"
}

# ; && || &

Sequential, conditional, and background separators. cmd1 && cmd2 runs cmd2 only if cmd1 succeeded; || is the inverse. & backgrounds the preceding command via BUILTIN_RUN_BG — fork + setsid, parent returns Status(0) immediately. Compiled with deferred short-circuit jumps to avoid double-compilation of the next command.

git pull && cargo test || echo "test failed"
sleep 30 &
echo "going to bg"

Arrays & Associative Arrays

13 topics · indexed arrays in executor.arrays, assoc in executor.assoc_arrays; argv splice via fusevm 0.10.1+.

# arr=(a b c)

Indexed-array literal assignment. Compile emits N element pushes + name push, then BUILTIN_SET_ARRAY (id 287) which writes a Vec<String> into executor.arrays and clears any prior scalar binding for the same name.

arr=(alpha beta "two words" gamma)
echo ${#arr[@]}    # 4

# arr+=(d e)

Append elements to an existing array (or create if missing). Routes through BUILTIN_APPEND_ARRAY (id 295) which extends executor.arrays[name].

arr=(a b)
arr+=(c d)
echo ${arr[@]}    # a b c d

# ${arr[N]}

Indexed access. zsh is 1-based for positive indices; negative indices count from the end (${arr[-1]} = last). Routes through BUILTIN_ARRAY_INDEX (id 289). For assoc arrays the same form does string-key lookup.

arr=(alpha beta gamma)
echo ${arr[1]}     # alpha
echo ${arr[-1]}    # gamma

# ${arr[@]}

Splice all elements as separate argv slots. Pushes a Value::Array which fusevm's Op::Exec/Op::ExecBg/Op::CallFunction and pop_args flatten into individual args. Without the splice, the array would collapse to one space-joined scalar.

arr=(--verbose --color=always /etc /var)
ls "${arr[@]}"     # ls --verbose --color=always /etc /var

# ${#arr[@]}

Number of elements. Routes through BUILTIN_ARRAY_LENGTH (id 291).

arr=(a b c d)
echo ${#arr[@]}    # 4

# for x in ${arr[@]}

Iterate over array elements. The for-loop word-list compile path calls BUILTIN_ARRAY_FLATTEN (id 293) which flattens nested arrays one level — so for x in start ${arr[@]} end produces N+2 iterations, not 3.

arr=(red green blue)
for c in ${arr[@]}; do
  echo "color: $c"
done

# arr=()

Empty-array literal. ${#arr[@]} returns 0; iteration runs zero times.

arr=()
for x in ${arr[@]}; do echo "never"; done
echo "len=${#arr[@]}"   # len=0

# typeset -A name / declare -A name

Declare an associative array. The executor's builtin_typeset handles -A and pre-creates the HashMap<String, String> entry in executor.assoc_arrays. Optional — name[key]=val creates the assoc on demand too.

typeset -A user
user[name]=Jacob
user[role]=eng
echo "${user[name]}"   # Jacob

# name[key]=value

Assoc-array key set. Compile detects name[key] shape on the LHS of an assignment and routes to BUILTIN_SET_ASSOC (id 288) which stores into executor.assoc_arrays[name][key].

db[host]=localhost
db[port]=5432
db[user]=admin
echo "${db[host]}:${db[port]}"

# ${name[key]}

Assoc-array lookup. BUILTIN_ARRAY_INDEX checks assoc_arrays first; if the name has an assoc binding the string key is used directly. Missing keys return empty string.

typeset -A m
m[a]=1; m[b]=2
echo "a=${m[a]} b=${m[b]}"
echo "missing=[${m[nope]}]"   # missing=[]

# ${(k)name}

List the keys of an associative array. Order is HashMap iteration order (implementation-defined). See the Zsh Parameter Flags chapter for the full flag set.

typeset -A m
m[apple]=1; m[banana]=2
for k in "${(k)m}"; do echo $k; done | sort

# ${(v)name}

List the values of an associative array. ${name[@]} on an assoc returns the same value list.

typeset -A m
m[apple]=1; m[banana]=2
for v in "${(v)m}"; do echo $v; done | sort

# m[k]=new overwrites

Re-assigning a key replaces the prior value. The set-builtin always inserts; use += for append-on-existing-key semantics.

m[k]=first
m[k]=second
echo "${m[k]}"   # second

# m[k]+=tail

Append onto an existing assoc value (string concat). If the key is missing, behaves like a plain set. Routes through BUILTIN_APPEND_ASSOC (id 298).

m[host]=localhost
m[host]+=":5432"
echo "${m[host]}"    # localhost:5432

Parameter Expansion

21 topics · 15 of 19 VarModifier variants lower to native fusevm ops via Op::ExpandParam; ${var:-default} + ${#var} have been native-lowered in compile_zsh.rs (Phase 1 steps 1–2); 4 remaining variants + stacked ZshFlags + $(cmd)/$((expr))/concat ${a}${b} still hit the runtime fallback or the untokenize_preserve_quotes bridge to the legacy ShellParser.

# ${var:-default}

Use default if var is unset or empty. Lowers to Op::ExpandParam(DEFAULT). The default expression is itself compiled (variables, command substitution, etc. all expand at use time).

name=${1:-anonymous}
log_level=${LOG_LEVEL:-info}
target=${1:-$(date +%Y%m%d)}

# ${var:=default}

Assign default to var if unset/empty, then expand. Lowers to Op::ExpandParam(ASSIGN). The variable is set in executor.variables as a side effect.

: ${CACHE_DIR:=$HOME/.cache}
echo "$CACHE_DIR"   # CACHE_DIR is now set

# ${var:?msg}

Print error and exit if var is unset/empty. Lowers to Op::ExpandParam(ERROR). Common in script preamble for required env vars.

: ${API_TOKEN:?must be set}
: ${DEPLOY_TARGET:?usage: deploy <target>}

# ${var:+alt}

Expand to alt if var is set and non-empty, else empty. Lowers to Op::ExpandParam(ALTERNATE).

flag=${VERBOSE:+--verbose}
cargo build $flag

# ${#var}

String length of scalar (or via ${(#)arr} the element count of an array — see Zsh Flags). Lowers to Op::ExpandParam(LENGTH).

name=Jacob
echo ${#name}   # 5

# ${var:offset:length}

Substring extraction. Lowers to Op::ExpandParam(SLICE). Negative offsets count from the end. Length is optional (omitted = to end).

s=abcdefgh
echo ${s:2:3}    # cde
echo ${s:-3}     # fgh    (last 3 chars)
echo ${s:2}      # cdefgh  (offset 2 to end)

# ${var#pat} / ${var##pat}

Strip shortest / longest matching prefix. # = shortest, ## = longest. Lowers to STRIP_SHORT / STRIP_LONG.

path=/usr/local/bin/zshrs
echo ${path##*/}   # zshrs   (basename via longest-prefix-strip)
echo ${path#*/}    # usr/local/bin/zshrs

# ${var%pat} / ${var%%pat}

Strip shortest / longest matching suffix. % = shortest, %% = longest. Lowers to RSTRIP_SHORT / RSTRIP_LONG.

file=archive.tar.gz
echo ${file%.gz}    # archive.tar
echo ${file%%.*}    # archive    (dirname via longest-suffix-strip)

# ${var/pat/repl}

Replace first match of pat with repl. Lowers to SUBST_FIRST. repl may use & for the matched text in some shells; zshrs follows zsh's literal-replace semantics.

s="foo bar foo"
echo ${s/foo/baz}    # baz bar foo

# ${var//pat/repl}

Replace all matches. Lowers to SUBST_ALL. Heavily used by zpwr-style idioms (path normalization, sigil replacement).

s="hello world hello"
echo ${s//hello/HI}     # HI world HI
path="/a/b/c"
echo ${path//\//.}      # .a.b.c

# ${var:u} / ${(U)var}

Uppercase. The :u postfix lowers to Op::ExpandParam(UPPER); the (U) flag form goes through BUILTIN_PARAM_FLAG and supports stacking with other flags.

x=hello
echo ${x:u}      # HELLO
echo ${(U)x}     # HELLO

# ${var:l} / ${(L)var}

Lowercase. Same dual-form pattern as upper.

x=HELLO
echo ${x:l}      # hello
echo ${(L)x}     # hello

# $?

Exit status of the last command. Routed through BUILTIN_GET_VAR("?") which reads vm.last_status synced into the executor.

cargo test
if (( $? != 0 )); then echo "tests failed"; fi

# $$

Current shell PID (std::process::id()).

tmpdir=/tmp/build_$$
mkdir -p "$tmpdir"

# $1 $2 … $@ $* $#

Positional parameters. $@ / $* expand to the full list (joined by IFS), $# is the count, numeric $N is the Nth positional. Native fast-path: "$@" and ${arr[@]} reach BUILTIN_GET_VAR / BUILTIN_ARRAY_ALL as Value::Array and spread through BUILTIN_ARRAY_FLATTEN.

greet() {
  echo "got $# args"
  for arg in "$@"; do echo "- $arg"; done
}
greet alpha "two words" gamma

# $! $_ $-

Auxiliary special params. $! = PID of the most recent backgrounded job. $_ = last argument of the previous command (or empty at script start). $- = current shell flag set as a single string. All routed through BUILTIN_GET_VAR.

# $RANDOM $SECONDS $EPOCHSECONDS $LINENO

Dynamic special parameters resolved on each read in get_variable:

  • $RANDOM — 15-bit pseudorandom int. Mixes nanos+pid via Knuth's hash, masked to 15 bits. No seedable state today (independent draws).
  • $SECONDS — seconds since shell start, derived from __zshrs_start_secs baseline.
  • $EPOCHSECONDS — Unix time, SystemTime::now().
  • $LINENO — current line in the script (falls back to 1 today; full line tracking is Phase G work).
tmp="/tmp/build_$$_$RANDOM"
echo "elapsed: $SECONDS s"
echo "now: $EPOCHSECONDS"

# $PIPESTATUS / $pipestatus

Per-stage exit codes of the most recent pipeline. BUILTIN_RUN_PIPELINE collects each stage's status and writes both pipestatus (zsh convention) and PIPESTATUS (bash convention). Useful for set -e-style scripts that need to differentiate which stage failed.

seq 100 | grep '^5' | wc -l
echo "${pipestatus[@]}"   # 0 0 0
false | true
echo "${pipestatus[1]}"   # 1   (zsh: 1-indexed)

# [[ -v var ]] — existence test

BUILTIN_VAR_EXISTS (id 306) checks every binding table — scalar variables, arrays, assoc_arrays, plus the process env — and returns true if any matches. Useful for "set but empty" vs "unset" disambiguation that -z conflates.

x=""
[[ -v x ]] && echo "set"        # set (even though empty)
[[ -z $x ]] && echo "empty"      # empty
unset x
[[ -v x ]] || echo "unset"        # unset

# $((expr))

Arithmetic substitution. The expression compiles through the inline arithmetic compiler — no runtime parser invocation. Integers are i64; supports + - * / % ** & | ^ << >> && || and pre/post inc/dec.

(( total = 1 + 2 * 3 ))
echo $((2 ** 16))    # 65536
echo $((0xff & 0x0f))

# $(cmd) / ` cmd `

Command substitution. zshrs runs the inner command on a nested VM and captures stdout via os_pipe::pipe() + dup2no fork. Trailing newlines are stripped per POSIX.

now=$(date +%s)
files=$(ls *.rs | wc -l)
echo "$files files at $now"

Zsh Parameter Flags

12 topics · ${(flags)name} form. Flags apply left-to-right; (jL) joins-then-lowercases, (s:,:U) splits-then-uppercases. Routes through BUILTIN_PARAM_FLAG (id 297).

# (L) — lowercase

Lowercases the value. For arrays, lowercases each element.

x=Hello
echo ${(L)x}   # hello

arr=(One Two Three)
echo ${(L)arr}   # one two three

# (U) — uppercase

Uppercases.

echo ${(U)x}   # HELLO

# (j:sep:) — join

Join array elements with sep. The delimiter char following j sets the bracket — :, ., ,, | are common. j with no delimiter joins with a single space (IFS-default).

arr=(one two three)
echo ${(j:-:)arr}   # one-two-three
echo ${(j:|:)arr}   # one|two|three
echo ${(j)arr}      # one two three

# (s:sep:) — split

Split a scalar on sep into an array. The result splices into argv via the standard array-flatten path.

csv="alpha,beta,gamma"
for x in "${(s:,:)csv}"; do
  echo "[$x]"
done

# (f) — split on newlines

Shorthand for (s:\n:) — split on newlines. Common with command substitution.

for line in "${(f)$(ls)}"; do
  echo "FILE: $line"
done

# (o) — sort ascending

Lexicographic sort.

arr=(charlie alpha bravo)
echo ${(o)arr}   # alpha bravo charlie

# (O) — sort descending

Reverse lexicographic sort.

echo ${(O)arr}   # charlie bravo alpha

# (P) — indirect

Use the value of the variable as another variable name and look that up. Useful for dynamic dispatch tables.

real=42
ref=real
echo ${(P)ref}    # 42

# (@) — force array

Coerce a scalar to a single-element array (so subsequent flags treat it as array-shape).

x=hello
echo ${#x}        # 5  (string length)
echo ${(#)${(@)x}}  # 1  (array length after @-coerce)

# (k) — keys of assoc

Returns the keys of an associative array as an array. Order is HashMap iteration order.

typeset -A m
m[apple]=1; m[banana]=2
for k in "${(k)m}"; do echo "$k"; done

# (v) — values of assoc

Returns the values of an associative array as an array.

echo ${(v)m}    # 1 2

# (#) — count

Element count for arrays, character count for scalars. Different from ${#var} in that it can be combined with other flags via stacking.

arr=(a b c d)
echo ${(#)arr}    # 4

echo ${(#L)x}     # length after lowercasing

# (q) / (qq) / (qqq) — quote

Wrap each value with shell-safe quoting. Consecutive qs raise the quoting level: q = POSIX single-quote (escaping inner '), qq = double-quote (escaping $/`/"/\), qqq = ANSI-C $'…' form (escaping control chars + backslashes).

x="hi 'world'"
echo ${(q)x}     # 'hi '\''world'\'''
echo ${(qq)x}    # "hi 'world'"
s=$(printf 'a\tb')
echo ${(qqq)s}   # $'a\tb'

# (g) — backslash-escape unwrap

Process backslash escapes (\n, \t, \r, \\, \xNN). Useful when reading lines from a config that stored real newlines as literal \n.

s='hello\nworld'
echo "${(g)s}"
# hello
# world

# (n) — natural-numeric sort

Compare digit runs as integers and other runs lexicographically. file2 sorts before file10 (vs lex order which would put file10 first). Combine with (o)/(O) for ascending/descending.

arr=(file10 file2 file1 file20)
echo ${(on)arr}    # file1 file2 file10 file20

# (i) — case-insensitive sort

Sort comparing lowercase forms while preserving original case in the output.

arr=(Banana apple Cherry)
echo ${(i)arr}    # apple Banana Cherry

# (t) — type query

Returns the variable's typeset shape: scalar, array, association, or empty (unset). Useful in scripts that branch on whether a name is an indexed vs assoc array.

arr=(a b)
typeset -A m; m[k]=v
sc=str
echo "${(t)arr}|${(t)m}|${(t)sc}"
# array|association|scalar

# (%) — prompt expansion

Process %F/%B/%f/%{/%} etc. via expand_prompt_string. Useful for storing prompt fragments in variables and rendering them at use time.

fragment='%F{cyan}>>%f '
echo "${(%)fragment}"

# (e) — re-evaluate

Run the value as a shell command and return its captured stdout. Equivalent to $(eval "$value") but uses the in-process pipe-capture path (no fork). Late-bound config strings.

cmd='echo "user is $(whoami)"'
echo "${(e)cmd}"   # user is jacob

# (p) — print-style escapes

Process backslash escapes the same way print -e does. Same set as (g) with \e / \E mapped to ESC.

fmt='\e[36mcolor\e[0m'
echo "${(p)fmt}"   # cyan "color"

Redirection & Pipelines

14 topics · pipelines fork-per-stage via BUILTIN_RUN_PIPELINE (id 285); redirects scoped via WithRedirectsBegin/End.

# cmd > file — write

Truncates and writes stdout to file. Bracketed in a redirect scope so subsequent commands see the original fd 1.

echo hello > out.txt

# cmd >> file — append

Appends stdout. Creates the file if missing.

date >> access.log

# cmd < file — read

Connects file to stdin.

sort < data.txt

# cmd 2> file — stderr

Redirect stderr (fd 2). Use 2>&1 to merge into stdout.

cargo build 2> build.err
make 2>&1 | tee build.log

# cmd &> file — write both

Redirect both stdout and stderr (zsh/bash extension). &>> appends.

./run.sh &> combined.log

# cmd <<EOF — heredoc (with variants)

Multi-line stdin literal. Three flavors:

  • <<EOF — variables, command sub, arithmetic all expand inside the body. Body trailing newline trimmed before HereString emit so stdin is byte-identical to source.
  • <<-EOF — leading tabs stripped from each body line and from the closing-terminator-line check. Lets you indent the heredoc body inside a function body.
  • <<'EOF' / <<"EOF" — quoted terminator → body is verbatim, no expansion. Detected by SNULL/DNULL markers in the lexer's terminator string; HereDocInfo.quoted drives compile-side behavior.

Body capture happens in a parser post-pass (fill_heredoc_bodies) — the lexer collects bodies into self.heredocs[] at process_heredocs (called on Newlin/Endinput); the parser records a heredoc_idx per redir during parse_redirection; the post-pass walks the AST resolving indices into ZshRedir.heredoc.

cat <<EOF
hello $USER
host: $(hostname)
EOF

cat <<-EOF
	indented
	body
	EOF

cat <<'EOF'
$USER stays literal
EOF

# cmd <<< "string" — herestring

Pass a single string (with trailing newline) as stdin.

tr a-z A-Z <<< "hello"   # HELLO

# cmd1 | cmd2 — pipeline

Connect cmd1's stdout to cmd2's stdin via a pipe. zshrs's pipeline is bytecode-native: each stage compiles to a sub-chunk; BUILTIN_RUN_PIPELINE creates N-1 pipes, forks N children, wires fds, runs each stage's bytecode on a fresh VM. SIGPIPE works correctly.

seq 100 | sort | uniq | wc -l

# cmd1 |& cmd2 — pipe stdout+stderr

zsh extension: pipes both fd 1 and fd 2 of the LHS to RHS.

cargo test |& head -40

# ! cmd — invert status

Negate the pipeline's exit status. ! cmd exits 0 if cmd failed, 1 if cmd succeeded.

! grep -q ERROR log   # status 0 means no ERROR found

# <(cmd) — process sub (input)

Spawn cmd, present its stdout as a path readable by the consumer. zshrs uses worker pool threads instead of fork — the FIFO/temp file is wired to a thread that runs the bytecode for cmd.

diff <(sort a.txt) <(sort b.txt)

# >(cmd) — process sub (output)

Spawn cmd, present its stdin as a path writable by the producer.

tar c . | tee >(gzip > backup.tgz) > backup.tar

# cmd N>&M — duplicate fd

Duplicate fd M to fd N. Common idiom: 2>&1 merges stderr into stdout. <&fd defaults the destination to fd 0; >&fd defaults to fd 1. Variable-expanded fd targets work: read line <&${COPROC[1]}.

find / 2>&1 | grep '\.zsh$' | head

coproc { echo from-child; }
read reply <&${COPROC[1]}
echo "$reply"   # from-child

# cmd N<&- / cmd N>&- — close fd

Close fd N. POSIX form for explicitly tearing down a previously-opened fd (typically after exec FD< file).

exec 5< data.txt
read line <&5
exec 5<&-     # close fd 5

# { cmds; } > file — block redirect

Apply a redirect to a compound command. WithRedirectsBegin/End bracket the inner ops so the parent shell's fds are restored after.

{
  echo header
  date
  uname -a
} > report.txt

POSIX / Zsh Builtins

52 topics · the full POSIX-required set plus zsh's interactive-shell builtins. Every entry routes through fusevm CallBuiltin(id, argc); arrays splice via pop_args.

# cd [dir]

Change directory. With no argument, goes to $HOME. cd - jumps to $OLDPWD. Updates $PWD.

cd ~/repos/zshrs
cd -      # back to previous

# pwd

Print working directory.

echo "now in $(pwd)"

# echo [args]

Print arguments space-separated with trailing newline. -n suppresses newline; -e enables backslash escapes.

echo hello world
echo -n "no newline"
echo -e "tab:\there"

# print

zsh's enhanced echo. -r raw, -n no newline, -l one arg per line, -z push to history.

print -l alpha beta gamma   # one per line
print -P "%F{cyan}cyan%f"   # prompt expansion

# printf format args…

C-style formatted output. Format directives: %s %d %f %x %o %b; backslash escapes \n \t etc.

printf "%-10s %5d\n" "items" 42

# export VAR=value

Mark a variable for export to subprocesses (writes to std::env).

export PATH="/opt/bin:$PATH"
export RUST_LOG=debug

# unset VAR…

Remove a variable. Removes from variables, arrays, assoc_arrays, and exported env.

unset DEBUG_FLAG
unset -f myfunc   # remove function

# source file / . file

Execute a script in the current shell. zshrs's source path uses bytecode caching: first run compiles + stores to SQLite; subsequent runs deserialize cached chunks. Plugin delta cache replays state changes (functions, aliases, vars, hooks, zstyles, options) in microseconds.

source ~/.zshrc
. /etc/profile.d/lang.sh

# exit [n]

Exit the shell with status n (default last status). Aliases: bye, logout. Compile emits a forward jump patched past chunk-end (halts the VM). EXIT trap fires before halt.

[[ -z $1 ]] && { echo "missing arg"; exit 2; }

# true / false / :

Status helpers. true and : exit 0; false exits 1. : is also the no-op for argument expansion side effects.

: ${VAR:=default}   # set if unset; expansion side effect, no command
while true; do …; done

# test EXPR / [ EXPR ]

POSIX conditional test. Use [[ … ]] for the zsh extended form (in-shell, no fork). Operators: -f -d -e -r -w -x -s -z -n = != < > -eq -ne -lt -le -gt -ge.

[ -f /etc/passwd ] && echo "exists"
[[ $count -gt 10 && -n $name ]]

# local VAR[=value]

Function-local scope. zshrs's local-scope semantics: declared vars are pushed onto a scope stack at function entry, popped at return. Assignment-without-declare-at-top-of-function is allowed (zsh idiom) but has caveats — see CLAUDE.md "Never use `local` inside loops".

my_func() {
  local count=0
  local result
  …
}

# typeset / declare

Declare a variable with attributes. -a indexed array, -A assoc array, -i integer, -r readonly, -x exported, -l lowercase, -u uppercase, -g global (in func scope), -f function-scoped.

typeset -i count=0
typeset -A config
typeset -r APP_VERSION=1.0
declare -a names=(alice bob carol)

# readonly VAR=val

Mark a variable read-only. Subsequent assignments error.

readonly RELEASE_BRANCH=main

# integer VAR=expr

Declare an integer-typed variable. Subsequent assignments evaluate as arithmetic.

integer i=10
i=i+5
echo $i    # 15

# float VAR=expr

Declare a float-typed variable.

float pi=3.14159
echo $((pi * 2))

# read VAR…

Read a line from stdin into one or more variables. Flags: -r raw (no backslash escapes), -n N N chars, -t SEC timeout, -s silent, -p prompt, -A arr read into array.

echo -n "name? "
read name
read -r line < /etc/hostname
read -A words <<< "alpha beta gamma"

# mapfile / readarray

Read each line of a file (or stdin) into successive elements of an array. Bash compat alias.

mapfile -t lines < /etc/hosts
echo "${#lines[@]} hosts"

# shift [n]

Remove the first n positional parameters (default 1).

process() {
  cmd=$1; shift
  do_thing "$cmd" "$@"
}

# eval string

Re-parse and execute the joined-args string as shell code. Single-quoted args defer expansion correctly (the lexer's \0-sentinel for single-quoted specials is honored by the compile path's trigger detection).

x=10
eval 'echo $x'    # 10  (expansion deferred to eval-time)
eval "$(starship init zsh)"

# exec [cmd args]

Replace the current shell with cmd. With no command, applies redirections to the current shell (e.g. exec > log redirects subsequent output).

exec > build.log 2>&1
exec /usr/local/bin/newshell

# command [-pvV] cmd

Bypass functions/aliases and run the underlying command. -v prints how the name would resolve; -V verbose form.

command rm /tmp/junk    # bypass user's `rm` function
command -v cargo        # /Users/wizard/.cargo/bin/cargo

# builtin cmd

Force builtin dispatch (skip functions/aliases).

builtin cd /tmp     # zshrs's cd, never a user override

# let "expr"

Evaluate arithmetic expression(s); exit 0 if last result is non-zero.

let "i = 1 + 2"
let "i++"

# set [-eux] [args…]

Set shell options or positional parameters. -e exit on error, -u nounset, -x trace, -o pipefail propagate pipeline status.

set -euo pipefail
set -- alpha beta gamma   # set $1 $2 $3

# setopt / unsetopt

Toggle zsh-style options. Options live in executor.options; setopt foo turns on, unsetopt foo turns off.

setopt extended_glob
setopt null_glob
unsetopt nomatch

# shopt

Bash-compat option toggling. Maps onto the same options table as setopt.

shopt -s globstar

# emulate [-LR] [shell]

Switch emulation mode (zsh, sh, ksh, csh). -L local-to-function, -R reset all options to that shell's defaults.

emulate -L zsh    # function-local zsh defaults

# getopts spec var

POSIX option parsing. Iterates flags from $@, sets $var to each option, $OPTARG to its value.

while getopts "vh:" opt; do
  case $opt in
    v) verbose=1 ;;
    h) host=$OPTARG ;;
  esac
done

# zparseopts

zsh's richer option parser. -A arr stores into an assoc array; supports long options.

zparseopts -A opts -- v h: c::
echo "${opts[-h]}"

# autoload [name…]

Mark a function for lazy loading from $fpath. zshrs's autoload path uses SQLite-cached bytecodes: first invocation deserializes a chunk; subsequent invocations skip lex/parse/compile entirely.

autoload -Uz compinit
compinit

# functions [name]

List defined functions, or print a function's body. +f prints names only.

functions               # all functions
functions my_func       # body of my_func

# unfunction name

Remove a function definition. Same as unset -f.

unfunction old_helper

# trap 'cmd' SIG

Install a signal handler. Special pseudo-signals: EXIT (run at shell exit), DEBUG (before each command), ERR (on non-zero status), ZERR (zsh-specific).

trap 'rm -rf $tmpdir' EXIT
trap 'echo got SIGINT' INT

# pushd / popd / dirs

Directory stack manipulation. pushd dir changes to dir and pushes onto the stack; popd pops and changes back. dirs -v lists.

pushd ~/work/proj
…
popd

# alias name='cmd'

Define a command alias. With no args lists all aliases. alias -g creates a global alias (substitutes anywhere on the command line).

alias ll='ls -lAh'
alias -g G='| grep'

# unalias [-m pattern] name

Remove an alias. -m matches by glob pattern.

unalias ll
unalias -m 'g*'   # remove all aliases starting with g

# type / whence / where / which

Resolve a name to its source: alias, function, builtin, or external. whence -p shows path only; whence -a shows all matches.

type ls
whence -a cargo
which gcc

# hash / rehash / unhash

Command-name → path cache. rehash rebuilds the cache from $PATH (parallel scan across worker pool). unhash -dm 'pat' removes named-directory entries by glob.

rehash             # after installing new binaries
hash               # show cache
unhash -dm 'tmp*'  # remove tmp* hashed dir refs

# ulimit / limit / unlimit

Resource limit query/set. -n open files, -s stack size, -u max processes, -c core dump.

ulimit -n 65536
ulimit -a    # show all

# umask [mask]

Set/show file-creation mask.

umask 077    # owner-only by default

# times

Print user + system times for the shell and its children.

times

# caller

Print the line + filename of the caller of the current function. Useful in error reporters and debuggers.

my_assert() {
  [[ $1 -eq $2 ]] || { echo "assertion failed at $(caller)"; exit 1; }
}

# help [name]

Print help for a builtin (or list builtins).

help echo
help

# enable / disable

Enable/disable a builtin by name. Useful for testing external-vs-builtin behavior.

disable cat       # falls back to /bin/cat
enable cat        # restore zshrs's cat builtin

# noglob cmd

Run cmd with glob expansion disabled for its arguments. Equivalent to setopt noglob; cmd; unsetopt noglob as a one-liner.

noglob find . -name '*.rs'   # don't pre-glob *.rs

# ttyctl -f / ttyctl -u

Freeze / unfreeze the tty state. Used by completion code that reads stdin.

ttyctl -f       # freeze
ttyctl -u       # unfreeze

# sync

Force the OS to flush its buffer cache to disk. Direct sync(2) syscall.

cp big.iso /mnt/usb/
sync

# zmodload [-i] mod

Load a zsh module. zshrs's modules are statically linked, so zmodload is a no-op-and-succeed for compat (-i idempotent flag).

zmodload zsh/datetime
zmodload zsh/regex

# zsleep n

Subsecond-precision sleep. zsleep 0.5 sleeps 500ms. Different from coreutils sleep only in supporting fractional seconds reliably across implementations.

zsleep 0.25       # 250ms

# zsystem subcmd

zsh/system module operations. zsystem flock file for file locking; zsystem getflags for flag dumps.

zsystem flock /tmp/lockfile

# strftime fmt [time]

Format a unix timestamp using strftime(3) directives. Default time is now.

strftime "%Y-%m-%d %H:%M:%S"

# mkdir [-p] dirs…

Create directories. -p creates parents and doesn't fail if exists.

mkdir -p ~/.config/myapp/{cache,logs}

# vared VAR

Edit a variable's value via the line editor. Handy for interactive config edits.

vared PATH

# zformat -f var fmt args…

Sprintf with %X:value argument bindings; common in completion display formatting.

zformat -f line "%name (%size)" name:foo size:42

# zregexparse

State-machine regex parser used by completion and filename rewriting.

zregexparse vname tname regex

# zcompile file

Pre-compile a zsh script to .zwc. zshrs treats .zwc as one of its bytecode-cache inputs and deserializes the chunk directly when sourcing the original file.

zcompile ~/.zshrc

Job Control & Background

11 topics · cmd & compiles to a sub-chunk + BUILTIN_RUN_BG (id 290) fork dispatch; full job-table integration is Phase G6.

# cmd & — background

Run cmd in the background. The compile path detects ListOp::Amp, compiles cmd into a sub-chunk, emits CallBuiltin(BUILTIN_RUN_BG, 1). The handler forks; child setsids and runs the chunk on a fresh VM; parent returns Status(0) immediately.

sleep 30 &
make all & make docs & wait

# jobs

List background jobs known to the shell. Note: pids spawned via BUILTIN_RUN_BG aren't yet registered in the JobTable (Phase G6); externally-launched bg cmds via std::process::Command::spawn() are.

jobs                # active jobs
jobs -l             # with pids

# fg [%jobspec]

Bring a background/stopped job to the foreground. %1, %2 select by job number; %name by command-name prefix.

fg %1
fg %vim

# bg [%jobspec]

Resume a stopped job in the background.

bg %1

# kill [-SIG] pid|%job

Send a signal. Default is SIGTERM. Builtin form supports both PID and jobspec.

kill -INT 12345
kill %1
kill -l            # list signals

# wait [pid|%job]

Wait for a child to exit; with no args, waits for all bg children. Status is the wait-on'd child's exit code.

worker1.sh & pid1=$!
worker2.sh & pid2=$!
wait $pid1 $pid2

# disown [%job]

Remove a job from the job table without killing it. The process keeps running but is no longer tracked.

long_task &
disown %1

# suspend

Stop the current shell (sends SIGTSTP to itself). Common idiom in nested shells.

suspend

# async 'cmd'

Ship work to the worker pool and return an opaque ID. Unlike &, this runs on a thread within the same process — no fork, no setsid.

id=$(async 'sleep 5; curl https://api.example.com')

# await ID

Wait for an async task by ID and capture its stdout.

id=$(async 'expensive-job')
result=$(await $id)

# $! — last bg pid

PID of the most-recently-backgrounded command. Used to wait/signal it later.

long_task &
echo "spawned $!"
wait $!

Anti-Fork Coreutils

23 topics · 2000-5000x faster than forking to /bin — every invocation runs in-process via direct syscalls.

# cat [files…]

Concatenate files to stdout. With no args, copies stdin. -n numbers lines; -A shows non-printable. No fork.

cat /etc/hosts
cat *.log | grep ERROR

# head [-n N] [files…]

First N lines (default 10). -c N first N bytes.

head -20 /var/log/system.log

# tail [-n N] [-f] [files…]

Last N lines. -f follow (in-process).

tail -f access.log

# wc [-lwc] [files…]

Line / word / char count.

wc -l *.rs        # total Rust LOC

# sort [-n -r -u -k] [files…]

Sort lines. -n numeric, -r reverse, -u unique, -k N sort on key.

du -h * | sort -hr

# find path [predicates]

Walk directories applying predicates. -name, -type, -size, -mtime, -exec. In-process walk.

find . -name '*.rs' -size +1k

# uniq [-c -d -u] [files…]

Filter adjacent duplicates. -c prefix counts, -d show duplicates only.

cat history.log | sort | uniq -c | sort -nr

# cut [-d delim] -f fields

Extract fields from each line.

cut -d: -f1 /etc/passwd     # all usernames

# tr SET1 SET2

Translate or delete characters.

echo HELLO | tr A-Z a-z
tr -d '\r' < crlf.txt > lf.txt

# seq [start] [step] end

Print a sequence of numbers.

for i in $(seq 1 10); do …; done
seq 0 0.5 5

# rev

Reverse each line character-wise.

echo hello | rev   # olleh

# tee [-a] file…

Copy stdin to stdout AND to one or more files. -a appends.

build.sh 2>&1 | tee build.log

# basename path [suffix]

Strip directory and optionally a trailing suffix.

basename /tmp/file.txt    # file.txt
basename /tmp/file.txt .txt   # file

# dirname path

Strip the trailing component.

dirname /tmp/file.txt    # /tmp

# touch [-a -m -t time] file…

Update file timestamps; create empty file if missing.

touch deploy.lock

# realpath path

Resolve symlinks and relative paths to a canonical absolute path.

realpath ./src/exec.rs

# sleep seconds

Pause for seconds (fractional supported).

sleep 0.5

# whoami

Effective username (direct geteuid + getpwuid syscalls).

echo "running as $(whoami)"

# id [-u -g -n] [user]

User/group ID info.

id -u           # numeric uid
id -un          # username

# hostname [-s -f]

System hostname. Direct syscall.

hostname            # short
hostname -f         # FQDN

# uname [-a -s -r -m]

Kernel name / release / arch. Direct uname(3).

uname -srm

# date [+fmt]

Print current date/time. +fmt uses strftime directives. Direct syscall.

date
date +%s
date "+%Y-%m-%d %H:%M:%S"

# mktemp [-d] [template]

Atomic temp file/dir creation.

tmp=$(mktemp)
tmpdir=$(mktemp -d)

Parallel Primitives

5 topics · all run on the persistent worker pool, not via fork. Bytecode-VM execution under rayon task scheduler.

# pmap 'cmd {}' args…

Parallel map across worker pool. Output preserves input order. The {} placeholder gets each arg.

pmap 'gzip {}' *.log
pmap 'sha256sum {}' **/*.iso

# pgrep 'pred {}' args…

Parallel filter. Predicate runs concurrently; matching args are collected in input order.

pgrep 'grep -q TODO {}' **/*.rs    # files containing TODO

# peach 'cmd {}' args…

Parallel for-each, unordered. Fire each task and don't preserve order — useful when output order doesn't matter.

peach 'convert {} {}.png' *.svg

# barrier 'cmd1' ::: 'cmd2' ::: 'cmd3'

Run all commands in parallel; wait for all to complete. Final status is the worst (highest) of all child statuses.

barrier 'cargo test' ::: 'cargo doc' ::: 'cargo audit'

# barrier --fail-fast …

Cancel remaining tasks as soon as one fails. Useful for guarded fan-out (compile, test, lint) where one failure makes the rest moot.

barrier --fail-fast 'rustfmt --check src/' ::: 'cargo clippy'

AOP Intercept

3 topics · the first shell with aspect-oriented programming. before/after/around advice. Routes through host_exec_externalrun_intercepts.

# intercept before pat { code }

Run code before any command matching pat. $INTERCEPT_NAME, $INTERCEPT_ARGS, $INTERCEPT_CMD are bound. Pattern is glob: git, git*, _*.

intercept before git { echo "[$(date)] $INTERCEPT_CMD" >> ~/git.log }

# intercept after pat { code }

Run after the matched command finishes. Adds $INTERCEPT_MS (elapsed) and $INTERCEPT_STATUS.

intercept after '_*' {
  echo "$INTERCEPT_NAME took ${INTERCEPT_MS}ms"
}

# intercept around pat { code } + intercept_proceed

Wrap the command. Call intercept_proceed from inside to invoke the original; skip it to suppress the original entirely. Replaces defer, profile, memo, retry, timeout with one primitive.

intercept around expensive_func {
  local cache=/tmp/cache_${INTERCEPT_ARGS// /_}
  if [[ -f $cache ]]; then
    cat $cache
  else
    intercept_proceed | tee $cache
  fi
}

Diagnostics & Profiling

5 topics · zshrs-exclusive observability builtins.

# doctor

Full diagnostic dump: worker-pool metrics, SQLite cache stats, bytecode coverage, registered intercepts, history db size, options table.

doctor

# dbview [table] [filter]

Browse SQLite caches without writing SQL. Lists tables + row counts; with arguments shows specific entries.

dbview                        # tables + row counts
dbview autoloads _git         # one autoload entry
dbview comps git              # completion search
dbview history docker         # history search

# profile cmd

In-process profiling with nanosecond accuracy. No fork, no strace. Reports CPU time, syscalls, allocations.

profile cargo build

# zprof

Function-level profile (zsh module compat). Enable with zmodload zsh/zprof; print accumulated stats with zprof.

zmodload zsh/zprof
source ~/.zshrc
zprof | head -20

# history [-n N] [pattern]

Browse command history. Backed by SQLite + FTS5 — full-text search, frequency-ranked, with timestamps and exit status per command.

history -10                   # last 10
history docker                # FTS search

Completion System

10 topics · compsys backed by SQLite FTS5. compinit pre-warms the autoload index across the worker pool.

# compinit

Initialize the completion system. zshrs's compinit is async: fpath is scanned + bytecode-compiled in the background while the prompt drops.

autoload -Uz compinit && compinit

# compdef func cmd

Bind a completion function to a command name.

compdef _docker docker
compdef _kubectl kubectl k

# compadd [-J group] words…

Add candidate completions. -J groups them; -d desc attaches descriptions.

compadd -J commands start stop status restart

# compset

Manipulate the completion state — peel off prefixes, set the position, etc.

compset -P 'http://'
compset -S '*.com'

# compopt

Toggle completion options for the current invocation.

compopt -o nospace

# compgen [opts] word

Bash-compat completion candidate generator.

compgen -c git           # all commands starting with "git"

# complete

Bash-compat completion definition.

complete -W "start stop status" myservice

# zstyle

Style configuration for completion + prompt. zsh's universal config DSL.

zstyle ':completion:*' menu select
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'

# cdreplay

Replay deferred compdef calls accumulated during async compinit.

compinit; cdreplay -q

# promptinit + prompt

Theme system for prompts.

autoload -Uz promptinit && promptinit
prompt redhat

ZLE Line Editor

4 topics · 26 ZLE widgets, syntax highlighting, autosuggestions, abbreviations.

# zle widget [-N name handler]

Manipulate the line editor. -N name handler defines a user widget; zle name invokes one.

my-widget() { LBUFFER+=" --verbose"; }
zle -N my-widget
bindkey '^Xv' my-widget

# bindkey [seq widget]

Bind a key sequence to a widget. -M map targets a specific keymap.

bindkey '^R' history-incremental-search-backward
bindkey -M vicmd 'k' up-line-or-beginning-search

# vared VAR (line-edit form)

Use ZLE to edit a variable's value interactively.

vared LS_COLORS

# Abbreviations

Fish-style abbreviations: type the abbrev + space, the full command expands inline. Configured via the fish/abbrs.rs backend.

abbr gco='git checkout'
abbr -g G='| grep'

PCRE & Regex

4 topics · zsh/pcre module support; the =~ operator inside [[ … ]] uses PCRE2.

# pcre_compile pattern

Compile a PCRE pattern; bound to $ZPCRE_OP.

pcre_compile '^(\d+)\s+(\w+)$'

# pcre_match string

Match against the most-recently compiled pattern. Captures land in $match, $mbegin, $mend.

pcre_compile '(\w+)@(\w+\.\w+)'
if pcre_match "alice@example.com"; then
  echo "user: ${match[1]}, domain: ${match[2]}"
fi

# pcre_study

Optimize the compiled pattern for repeated use.

pcre_compile '\b\d{4}-\d{2}-\d{2}\b'
pcre_study

# [[ string =~ pattern ]]

POSIX/PCRE regex match inside the conditional. Routed through Op::RegexMatch (host method regex_match).

if [[ $version =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
  major=${match[1]}; minor=${match[2]}; patch=${match[3]}
fi

Recently Closed Compat Gaps (eighty-eighth-pass)

46 constructs · all verified against ~/forkedRepos/zsh/Src/ via direct C-source ports. Each entry is pinned by at least one assertion in tests/zshrs_shell.rs; total: 968+ tests. These were the highest-impact gaps in man zshall coverage that remained after the seventy-seventh pass — most are param expansion, pattern matching, function scoping, and trap behavior.

Glob & Pattern

# extendedglob ~ exclusion at PATH level

Direct port of pattern.c P_EXCLUDE: matches RHS as a pattern against each LHS candidate's basename + full path, not as a separate CWD glob.

setopt extendedglob
echo /tmp/dir/*.txt~*README*   # all .txt except README.txt

# /^pat at any path component

Negation operator now triggers in any path component, not just the leading word.

setopt extendedglob
echo /tmp/dir/^skipme   # everything in /tmp/dir except files matching skipme

# $D/*, $D/(a|b) — glob meta after var ref

New BUILTIN_GLOB_EXPAND (id 343): pops a scalar pattern, runs expand_glob, pushes Value::Array. Compile path detects glob meta in literal segments only (so $?/$#/etc don't trigger).

D=/tmp/build
echo $D/*       # globs after $D substitution
echo $D/(a|b)   # alternation after $D substitution

# **/* recursive sort by full path

Recursive globs sort by full-path lex order (zsh's depth-first walk). Non-recursive globs keep basename-sorted output.

echo /tmp/dir/**/*  # f, sub, sub/g — not f, g, sub

Parameter Expansion

# ${${a%.txt}#hel} — outer strip on inner result

Direct port of subst.c getarg machinery — same dispatch for inner and outer.

a=hello.txt
echo "${${a%.txt}#hel}"   # lo

# ${(s. .)${(j. .)a}} — outer flag on inner expansion

Flag handler now recurses on leading ${ in rest; applies U/L/C/Split/Join/SplitWords/SplitLines to inner result.

a=(a b c)
echo "${(s. .)${(j. .)a}}"   # a b c (split-then-rejoin)

# ${(flags)$(...)} — cmd-subst as flag operand

Branch added for cmd-subst operands. (P) indirect honored: captured output is treated as a variable name and looked up.

a=hi; echo "${(P)$(echo a)}"   # hi  (NOT "a")
echo "${(U)$(echo hello)}"     # HELLO
echo "${(z)$(echo a b c)}"     # a b c

# ${(U)${(s. .)s}[N]} — [N] subscript after inner expansion

Splits the flag-applied joined-scalar back to parts, indexes, re-applies case-transform flags.

s="x y z"
echo "${(U)${(s. .)s}[1]}"   # X

# ${(l:N::s2:)val} — pad with empty s1 + s2 fallback

Pad parser collects both strings; when s1 empty and s2 given, s2 acts as fill char.

echo "${(l:5::0:)42}"   # 00000  (zsh-faithful)

# ${(j[+])a}, ${(s[|])s} — bracket-pair flag delimiters

zsh subst.c get_strarg accepts matched bracket pairs as flag delimiters: [/], {/}, (/), </>. Both flag-parser sites updated.

a=(a b c)
echo "${(j[+])a}"      # a+b+c
echo "${(j<X>)a}"      # aXbXc
echo "${(s[|])\"x|y|z\"}" # x y z

# :Q modifier — backslash escape removal

hist.c remquote: strips paired '/" AND \X backslash escapes. Both :Q paths fixed.

a="a\\ b"
echo ${a:Q}   # a b

# ${a//\X/repl} — backslash unescape for non-meta chars

Pattern pre-pass strips \X when X is not a glob meta. \?/\*/\[/\]/\(/\)/\|/\\ still escape.

a="x:y:z"
echo "${a//\:/-}"     # x-y-z
echo "${a//\./X}"     # works on dots too

# "${(o)a[@]}", "${(O)a[@]}", "${(n)a[@]}" — sort flags survive DQ when [@] given

Compile path encodes the at-subscript context through a new \u{03} sentinel in the flags string so the runtime DQ-stripper preserves array-only flags.

a=(c a b)
echo "${(o)a[@]}"      # a b c
echo "${(O)a[@]}"      # c b a
a=(10 2 1 22)
echo "${(n)a[@]}"      # 1 2 10 22

# ${SECONDS-default} — zsh-special params always-set

Whitelist treats SECONDS, EPOCHSECONDS, EPOCHREALTIME, RANDOM, LINENO, HISTCMD, PPID, UID, EUID, GID, EGID, SHLVL as set even when not in self.variables. HISTCMD getter also added.

echo "${SECONDS-default}"   # 0  (or current value)
echo "${UID-default}"       # 501
echo "${HISTCMD-default}"   # 0

Arrays & Subscripts

# Associative-array key insertion order

Storage switched from HashMap to indexmap::IndexMap so ${(k)h} / ${(kv)h} iterate in insertion order matching zsh's HashTable hnodes.

typeset -A h
h=(a 1 b 2 c 3)
echo ${(k)h}       # a b c
echo ${(kv)h}      # a 1 b 2 c 3

# for k v in arr — multi-name for loop

Direct port of parse.c par_for: parser collects all leading identifiers; compiler emits N-stride iteration with empty-string fill on short tail.

arr=(a 1 b 2 c 3)
for k v in $arr; do echo "$k=$v"; done

typeset -A h=(a 1 b 2)
for k v in ${(kv)h}; do echo "$k=$v"; done

# b=("${a[@]}") — array-to-array splice preserves boundaries

New scalar_assign_depth separate from assign_context_depth — only scalar RHS forces JOIN_STAR. Array init keeps splice.

a=("1 2" "3 4")
b=("${a[@]}")
echo ${#b}   # 2  (preserves both elements)

# $a[@], $a[*] — bare (no-braces) splice

array_splice_ref extended to accept the no-braces form, identical semantics to ${a[@]}.

a=(x y z)
printf "%s\n" $a[@]    # x, y, z (3 lines)
f() { echo $#; }; f $a[@]   # 3

# b="${a[@]}" — scalar assignment joins via JOIN_STAR

Compile path forces BUILTIN_ARRAY_JOIN_STAR when scalar_assign_depth > 0 (zsh subst.c forces single-string for scalar RHS).

a=(1 2 3)
b="${a[@]}"
echo $b   # 1 2 3

# a[$n]=() — variable subscript element-remove

Compile path now routes the subscript through compile_word_str when key has $ or backtick.

a=(1 2 3 4)
n=3
a[$n]=()       # remove 3rd element
echo "${a[@]}" # 1 2 4
a=(1 2 3 4)
a[$#a]=()      # remove last
echo "${a[@]}" # 1 2 3

# ${h[(I)pat]}, ${h[(R)pat]} on assoc — return ALL matches

(I)/(R) return all matching keys/values space-joined; (i)/(r) return first. (i)/(I) search keys; (r)/(R) search values.

typeset -A h=(a 1 b 2)
echo "${h[(I)*]}"      # a b
echo "${h[(i)*]}"      # a
echo "${h[(I)a]}"      # a (single match)
typeset -A h=(a 1 b 1 c 2)
echo "${h[(R)1]}"      # 1 1

# typeset -a a preserves existing array at top scope

Bare-declaration path now guards with in_function || !exists. typeset -aU dedupes in place.

a=(1 2 3); typeset -a a; echo $a       # 1 2 3 (was empty)
a=(a b a c b); typeset -aU a; echo $a  # a b c (was empty)

Arithmetic

# ((i=a[2])) — RHS array subscript pre-resolve

[ added to needs_eval trigger so MathEval (which runs pre_resolve_array_subscripts) handles it.

a=(10 20 30)
((i=a[2])); echo $i              # 20
((sum=a[1]+a[2]+a[3])); echo $sum # 60

# ((h[a]++)), ((h[a]+=v)) — compound ops on assoc

Direct port of zsh's math.c LVAL_NUM_SUBSC: subscript receiver retains lvalue identity through compound operators.

typeset -A h
h[a]=5
((h[a]++))      # h[a] = 6
((h[a]+=10))    # h[a] = 16

# ((++a[i])), ((++h[k])) — pre-increment on subscript

New parse_subscript_arith_pre_inc + compile-side subscripted_arith_compound_check accepts the pre-op shape. Pre-op returns NEW value, post-op OLD.

a=(10 20 30)
((++a[2])); echo $a   # 10 21 30
typeset -A h
h[a]=5
((++h[a])); echo $h[a] # 6

# ((a = cond ? T : F)) — ternary in assignment

? added to needs_eval triggers so MathEval handles ternary fully. ArithCompiler can't write back through ?:.

((a = 5 > 3 ? 99 : 0)); echo $a   # 99

# abs/min/max/int/floor/ceil/trunc — int-preserving

Int-input return int (not 5.). Float input still returns float.

echo $((abs(-5)))      # 5  (was 5.)
echo $((max(3,5,7)))   # 7
echo $((abs(-5.5)))    # 5.5  (still float)

Conditionals & Parser

# [[ a == a && (b == b || c == c) ]] — cond grouping parens

Lexer's incondpat resets on &&, ||, (, ), !, ]] per cond.c par_cond_3.

[[ a == a && (b == b || c == c) ]] && echo y

# case W in (P|Q)) BODY ;; — wrapped pattern with alternation

Parser accepts both bare (P) BODY and wrapped (P)) BODY (the (...) is the pattern wrapper, the second ) is the arm-close).

case foo in
  (foo|bar)) echo y;;
  (*)) echo n;;
esac

Functions & Scoping

# . file.sh ARG1 ARG2 — source passes positionals

Save outer positional_params, install args[1..] as new positionals, restore on exit.

# file.sh: echo "$1=$2"
. ./file.sh hi bye   # hi=bye

set -- a b c
. ./file.sh inner    # inside file.sh: $1=inner
echo "$@"            # outside: a b c (preserved)

# typeset -A h=(...) in function shadows outer

New local_assoc_save_stack mirrors local_array_save_stack lifecycle. Both call_function paths (legacy + bytecode) updated.

typeset -A h=(a 1)
f() { typeset -A h=(b 2); echo $h[b]; }
f                       # 2
echo $h[a]              # 1 (outer preserved)
echo "[${h[b]-empty}]"  # [empty] (inner didn't leak)

# Subshell umask snapshot+restore

SubshellSnapshot snapshots libc::umask; subshell_end restores. zsh forks for (...) so umask dies with child; we run in-process so we manually reset.

umask 022
(umask 077)   # subshell
umask         # 022 (parent unchanged)

# Subshell EXIT trap fires at subshell end

Was firing at process exit. Now fires before parent continues. SubshellSnapshot snapshots+restores parent traps; inner trap fires with traps.remove("EXIT") so the inner execute_script doesn't recurse.

(trap "echo trapped" EXIT; true)
echo done   # output: trapped\ndone (was: done\ntrapped)

Brace & Word Expansion

# {one,${a},three} — brace expansion after var substitution

Segment-concat path now emits BUILTIN_BRACE_EXPAND after concat when any literal segment contains { or }.

a=hi
echo {one,${a},three}        # one hi three
echo pre{1,${a},2}post       # pre1post prehipost pre2post

Builtins & I/O

# print -C N — column-padded output

Per-column width padding with 2-space separator (zsh format). Trailing partial rows skip padding after the last present item.

print -C 2 a b c d
# a  c
# b  d
print -C 2 alpha beta gamma delta
# alpha  gamma
# beta   delta

# read -e / read -E — echo line

zsh's bin_read calls fputs(buf, stdout) under both. -e echoes only (no assign); -E echoes AND assigns.

echo "abc" | { read -E v; echo "[$v]"; }
# abc
# [abc]
echo "abc" | { read -e v; echo "[$v]"; }
# abc
# []

# trap "..." ZERR / trap "..." ERR — fires on non-zero status

Wired into BUILTIN_ERREXIT_CHECK. Runs the trap body before the errexit decision; last_status saved/restored to prevent recursion on trap-body failures.

trap "echo zerr" ZERR
false
echo done
# zerr
# done

// generated against fusevm 0.12.1 dispatch · zshrs v0.10.1 · 15 chapters · 968+ tests · MenkeTechnologies