R/B84

No major shell dialect beyond the POSIX subset has a definition other than its implementation

A survey of how shell languages are specified, tested, and reimplemented. The POSIX subset is specified because multiple implementations had to interoperate; the bash, zsh, and fish dialects are not, because each had exactly one. Four consequences follow for testing, reproducibility, and generalizability, and a reimplementation wave now underway is generating the interoperability pressure that produces specifications.

part one

Scope

This artifact records the state of shell specification, testing, and reimplementation as of August 2026. It compiles published release notes, project documentation, academic work, and repositories read directly. It contributes no new experiment and proposes nothing.

The central observation is that the absence of a formal definition for the bash dialect is not an omission but a structural consequence of how single-implementation languages evolve, and that every downstream gap recorded below traces to it.

Why the definitional gap exists

The Bourne shell of 1977 was never formally specified; its semantics were what its C implementation did. Bash, released in 1989 as the GNU Project’s reimplementation and extension of Bourne, accreted features over approximately thirty-five years, largely under a single maintainer: arrays, [[ ]], extended globbing, process substitution, coprocesses, associative arrays. Each feature’s semantics, including its interactions with every other feature, were fixed by the implementation at the moment of merge. The manual pages and texinfo manual describe intent, and where documentation and code disagree the code prevails, because deployed scripts depend on actual rather than documented behaviour.

One subset was formalised, and the reason is instructive. POSIX specifies the sh subset because multiple independent implementations needed to interoperate: dash, ash, ksh, busybox sh, and bash in POSIX mode. Bash extensions, zsh extensions, and fish extensions never faced that pressure, because each had exactly one implementation. A single implementation is a complete and precise definition of its own language. It is not a human-readable or independently checkable one.

The pattern is general rather than particular to shells. CPython defined Python, Perl 5 defined Perl, and the Matz interpreter defined Ruby, each for decades. Formalisation work historically begins when a second implementation is attempted.

part two

The reimplementation wave

Second implementations are now being attempted, which is what makes the definitional question live.

table 1
Reimplementation and rewrite efforts as of mid-2026, with the specification each used. Every project listed used an existing binary as its executable specification.
ProjectLanguageSpecification usedValidation
brushRustthe bash binaryapproximately 1,700 oracle-based compatibility tests
Oils / OSHC++, Pythonthe bash binaryspec tests across bash, dash, mksh, zsh, OSH
fish 4.0Rust, from C++the previous fish binarythe existing test suite
sushRustthe bash binarya maintained fork of the GNU bash repository

The fish case is the clearest instance of the pattern. The project ported its entire codebase from C++ to Rust incrementally within one repository, using its own prior binary as the oracle. Maintainers rewriting their own shell could not point to a document defining it.

Four consequences

Specification. No conformance suite or formal semantics exists for the full bash dialect. Behaviour in extended features, and particularly in interactions between features, contains emergent cases nobody designed: errexit inside && chains, word splitting against the field separator, quoting inside pattern substitution, arithmetic evaluation order. Formalising it presents a choice between blessing historical accidents as law and declaring some behaviours defects, which would break deployed scripts. Neither maintainers nor users have had an incentive to resolve it, because the question of what bash does is answerable by running bash.

One academic exception exists. Smoosh provides a formal, mechanised semantics for the POSIX shell and demonstrates that mechanised shell semantics are tractable.3 Its scope is POSIX rather than the bash dialect.

Testing. GNU bash ships a regression suite, accumulated as defects were fixed rather than derived from the language surface. No coverage metric against the dialect exists, because the dialect has no enumerable definition to measure against. The state of the art is therefore oracle-based differential testing: identical inputs are run on the implementation under test and on reference shells, and outputs and exit codes are compared.

The brush project’s strategy converts unknown gaps into countable ones: when a missing feature is found, a failing test is committed, so the gap becomes an artifact rather than folklore.1 The Oils spec tests run thousands of cases across five shells and publish per-shell passing counts against bash, which is the closest existing approximation to a quantitative cross-shell compatibility benchmark; brush was added to that testing in 2025.2

The limitation applies to all of them. A pass rate quantifies gaps relative to a finite test set, and no test set covers the dialect. A claim that an implementation is nearly feature complete is not falsifiable, because the denominator does not exist.

Reproducibility. Because semantics reside in the implementation, behaviour varies across bash versions, with documented user-visible incompatibilities between 5.0, 5.1, 5.2, and 5.3; across build configurations; and across the boundary between shell builtins and external utilities, where a builtin test and /bin/test are distinct programs. A script’s behaviour is reproducible only modulo an exact version and environment. Oracle-based testing inherits the problem directly: the specification changes whenever the oracle is upgraded.

Generalizability. Findings about one shell rarely transfer, because each dialect is an independently accreted artifact. Where two shells share a feature the behavioural details differ, which is why the Oils framework encodes per-shell expected outputs. Any method, tool, or formal result built for one dialect must be substantially re-derived for the next.

part three

Rate of change

The intellectual framework of implementation-as-definition, regression-suite testing, and documentation-as-intent has been substantially unchanged since the late 1980s. Three developments constitute the movement since, all within roughly the last decade and concentrated in the last three years: the Oils project’s systematic empirical spec-test methodology, Smoosh’s demonstration that formal POSIX semantics are mechanisable, and the reimplementation wave recorded in table 1.

That wave is the first sustained interoperability pressure applied to the bash dialect specifically. On the historical pattern, interoperability pressure is the condition under which specifications get written.

Open questions

  1. Coverage of the behavioural surface. Whether grammar-based or coverage-guided differential fuzzing across bash, brush, and OSH could estimate how much of the behavioural surface existing suites exercise, with structural coverage of bash’s C source as a proxy denominator.
  2. Oracle stability. How differential suites should pin and track oracle versions, and whether a consensus semantics derived from majority agreement across several shells would be a more stable reference than any single binary.
  3. Mechanised semantics beyond POSIX. What extending Smoosh-style mechanised semantics to the bash extensions would require, and whether the result would be descriptive, blessing accidents, or normative, declaring defects.
  4. Feature usage in real scripts. Whether corpus studies mining scripts from package repositories could rank features by actual usage, allowing compatibility to be measured against scripts that exist rather than against an unbounded dialect.
  5. Whether a specification crystallises. Whether the current reimplementation wave generates sufficient pressure to produce a de facto bash specification, potentially out of an existing spec-test corpus.

related
E/2D8
The shell needs a separation of concerns
One program reads the text, expands it, decides what it may do, and runs it, with no boundary between any of those jobs. On separating them, and on the interface that appears where the seam is cut.
R/9B1
Three agent harnesses drive the same shell through three separately built integration layers
Executable resolution, invocation contracts, emitted shell constructs, state carry, and approval units for three harnesses, with the code each spends on them.

sources

Project documentation, release notes, and repositories consulted August 2026, with one academic source.

1
reubeno. brush: release notes, docs/reference/compatibility.md, integration testing documentation
v0.4.0, May 2026 · github.com/reubeno/brush
2
Oils project. Spec-test wiki, published shell compatibility reports, and methodology posts
oils.pub · oils.pub
3
Greenberg, M. et al. Smoosh: a formal, mechanized semantics for the POSIX shell
POPL, 2020
4
Free Software Foundation. GNU bash source distribution: tests/, the POSIX file, and CHANGES and COMPAT notes on inter-version incompatibilities
bash 5.x
5
fish-shell project. Retrospectives on the fish 4.0 port from C++ to Rust
2025
6
sush. rusty_bash, and the bash_for_sush_test repository maintained for running bash’s own suite against it
read 2026