Repository navigation
v0.0.32
A shell parser, interpreter and formatter in Go. One core — the common
denominator of real shells — with bash, zsh, ksh, dash and ash
as presets over a semantics vector rather than forks of one another.
This is a 0.x tag and means it. Early, and breakage is expected.
What changed since the last tag is the changelog below.
What is in the archive
Seven binaries: the substrate driver sh, the five dialect binaries, and
shfmt, which lays a script out in whichever dialect it was written in.
They install into libexec and never into a bin directory — the
shells are named after the shells they model, so a directory ahead of
/bin on PATH would answer for every program on the machine that
resolves a shell by name, and shfmt stays out of bin so that
homebrew-core's formula of the same name can still be installed.
brew install blairham/tap/sh
Where it stands
Each dialect graded against the real shell it models, over 4488 cases —
make conformance-dialects:
| binary | against | exact | behavioral |
|---|---|---|---|
bash |
bash 5.3.20 | 98% | 100% |
zsh |
zsh 5.9.2 | 98% | 99% |
dash |
dash | 98% | 100% |
ksh |
ksh93 AJM 93u+ | 96% | 99% |
ash |
BusyBox 1.37 ash | 93% | 99% |
Exact is byte-identical stdout, stderr and status; behavioral allows a
diagnostic to be worded differently when the status and output agree. The
distance between the two columns is almost entirely message wording.
What is not there yet
The README's "What is not there yet" is the current list, and it is real.
The sandbox contains the shell rather than the process tree, and there is
no Windows target. Diagnostic wording is a long tail rather than the
headline gap: a bounded sample of twenty everyday error situations — a
missing command, cd onto a file, a redirect that cannot open, an unknown
option to six builtins — comes back byte-identical on standard error and
exit status for every one of the five, 100 of 100 on 2026-09-27. The probe
is internal/cmd/diagsample and takes a few seconds a column, so this line
can be re-measured rather than inherited.
Changelog
The interactive shell
- zle: a POSTDISPLAY is not an edit to the line's region_highlight offsets (#6413)
The language and the interpreter
- interp, zsh: answer ${+parameters} without building the table (#6419)
- interp: a timed read of a regular file reads it in blocks (#6418)
- interp: keep a process substitution's shell end inside the first fd table (#6408)
- interp: read a quoted value's pattern in linear time under extendedglob (#6416)
- interp: render a condition's operands for the trace only when tracing (#6417)
- syntax: ask whether a command's words are a name list once per word (#6415)
Full Changelog: v0.0.31...v0.0.32