Skip to content

v0.0.39

Choose a tag to compare

@github-actions github-actions released this 01 Nov 03:06
· 112 commits to main since this release
v0.0.39
d067d9f

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, on macOS against the
golden record make oracle writes, 2026-10-04, 4488 cases each:

binary against exact behavioral
bash bash 5.3.20 99.8% 99.8%
zsh zsh 5.9.2 99.8% 99.8%
dash dash 99.9% 99.96%
ksh ksh93 AJM 93u+ 99.1% 99.5%
ash BusyBox 1.37 ash 99.5% 99.6%

Exact is byte-identical stdout, stderr and status; behavioral allows a
diagnostic to be worded differently when the status and standard output
agree. Every case still short of exact is listed per dialect in
internal/oracle/testdata/conformance/ with a written reason, and CI
fails a change that adds to a list.

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

Dialects

  • ksh: ${a[k]=x} and ${a[k]?m} make a missing table key before the test (#7213)
  • ksh: a declaration names itself for an operand it reads itself (#7207)

The language and the interpreter

  • interp, ksh: open a bare <> on standard output in ksh93 (#7219)
  • interp: a refused element store leaves the trace bash and ksh93 leave (#7190)
  • interp: ask the dialect about a read -n of a pipe exec opened, and one that finds the end (#7217)
  • interp: evaluate an absent name's subscript once, and stop arithmetic whose text did not expand (#7191)

Other

  • spec: record the refused store into a frozen indexed array as the allocated slot's third route (#7214)
  • tests: ask the child whether a closed stream is open, not cat and echo (#7200)
  • tests: send ksh/second signal, both ways its INT with a block still to come (#7215)

Full Changelog: v0.0.38...v0.0.39