A bash-compatible shell written in Rust, working toward being a drop-in replacement for everyday interactive and scripting use. huck implements most of bash's surface — expansions, control flow, functions, arrays, job control, line editing, programmable completion — and verifies it against real bash: every feature ships with a design spec, an implementation plan, a test suite, and a byte-identical bash-diff harness that runs the same fragments through both shells and asserts identical output.
Real-world bar: huck sources a non-trivial ~/.bashrc (bash-completion, git
prompt, nvm, mise activation) and drives interactive tab completion against the
system bash-completion package.
Homebrew (macOS/Linux):
brew install jdstanhope/huck/huck
Debian/Ubuntu (.deb):
curl -fsSL https://raw.githubusercontent.com/jdstanhope/huck/main/scripts/install.sh | sh
# or, manually:
sudo apt install ./huck_<version>_<arch>.deb
From source:
cargo install --git https://github.com/jdstanhope/huck huck
Actively developed, one coherent feature at a time. Current scope:
- ~3,400 tests (unit + integration) and 160 bash-diff harnesses, all
green;
cargo clippy --all-targetsclean. - Command-substitution-heavy scripts run at near-bash speed: per-
$()Shell clone is O(1) via copy-on-write (Rc + make_mut); 2000×$(true)after loading nvm: ~0.7 s vs ~46 s pre-fix;nvm lswall-clock now matches bash. - Sources
~/.bashrc-class startup files and the systembash-completionframework without errors. - Known gaps and actionable divergences are tracked as GitHub issues labelled
divergence; deliberate, kept-by-design divergences live indocs/bash-divergences.md— see Known differences from bash below for the summary.
The full feature history (every iteration's spec and plan) lives in
docs/superpowers/.
cargo build --release
cargo run # interactive REPL
cargo test # full test suiteCommand syntax & operators
Simple commands and pipelines (a | b); lists with ;, &&, ||, and &
(background, including backgrounding an and-or group and & as a list
separator); grouping with ( … ) (subshell) and { …; } (current shell);
redirections <, >, >>, >| (force-clobber), 2>, 2>>, &>, &>>,
fd duplication (2>&1, 1>&2), here-documents (<<, <<-), and
here-strings (<<<); redirections on
compound commands (while …; done > file). Comments (#), line continuation
(\), and multi-line input (an open quote/expansion/compound/operator carries
onto a > continuation prompt).
Expansions
- Parameter:
$VAR,${VAR}, positional ($1,${10},$@,$*,$#), specials ($?,$$,$!,$0,$-,$PIPESTATUS). - Modifiers:
${v:-w}/:=/:?/:+(and non-:forms), length${#v}, prefix/suffix strip${v#p}/##/%/%%, substring${v:off:len}, pattern substitution${v/p/r}/////#//%, case modification${v^^}/,,/^/,, transforms${v@Q}/@P/@U/@L/@u/@E, indirection${!v}, prefix-name and array-key${!a[@]}. - Arithmetic:
$((…)),((…)), and C-stylefor ((;;))— full operator set including bitwise, assignment,++/--,**, ternary, comma, non-decimal bases. - Command substitution
$(…)and`…`. - Brace expansion
{a,b}/{1..9}/{a..z}(nested, stepped, zero-padded); tilde~,~user,~+,~-; pathname globbing*,?,[…],[!…],[^…], POSIX classes[[:alpha:]], andextglob(?(…)/*(…)/+(…)/@(…)/!(…)) for both matching and pathname generation; word-splitting on$IFS.
Control flow & functions
if/elif/else, while/until, for (word-list, "$@", and C-style),
select, case (with ;; / ;& / ;;&); break N / continue N. Functions
in both name() { … } and function name { … } forms, with positional args,
local (and declare/typeset attributes), return, and dynamic scoping.
[[ … ]] extended test: glob ==/!=, regex =~ (populating BASH_REMATCH),
-v, -o optname, file/string/integer tests, &&/||/!/grouping.
Variables & arrays
Scalars, indexed arrays (a=(x y), a[i]=, a+=, ${a[@]}, ${!a[@]},
slicing), associative arrays (declare -A), integer (-i), readonly (-r),
export (-x) attributes; declare -g; printf -v.
Job control
Foreground and background process groups, tcsetpgrp terminal handoff (so
vim/less and Ctrl-Z work), SIGCHLD reaping with [N] Done notices, and
jobs/fg/bg/wait/kill/disown with %N/%+/%%/%-/%cmd/bare-PID
specifiers.
Line editing, history & completion
A line editor with history (persisted to $HISTFILE), history expansion
(!!, !n, !str, !$, ^old^new^, …), and programmable tab completion:
command/file/variable completion plus the full complete/compgen/compopt
machinery (-F functions, -W wordlists, action sets, -D default), which
drives the system bash-completion framework.
Builtins & options
cd, pwd, echo, printf (incl. %q), read, test/[, [[, export,
readonly, local, declare/typeset, unset, set (-e/-u/-x/-f/
-o/-o pipefail/-C/-o noclobber/set --/shift), shopt, getopts, eval, command,
hash, trap (EXIT/ERR/DEBUG/RETURN + signals), alias/unalias, jobs,
fg, bg, wait, kill, disown, history, break, continue, return,
exit, complete/compgen/compopt.
huck targets byte-identical behavior with bash 5.x. The remaining differences
are tracked as GitHub issues: open bugs and missing features are labelled
divergence
(filter by bug/enhancement and sev:high/sev:medium/sev:low), and
deliberate divergences kept by design are the closed
by-design
issues — also mirrored, with rationale, in
docs/bash-divergences.md. The issue tracker is the
single source of truth; this README deliberately does not duplicate the list (it
would only drift out of date).
Note: a fully working mise<TAB> additionally requires bash-completion
2.12+ (mise's generated completion calls the 2.12 API); on systems with
2.11 it falls back the same way it does under bash.
huck is a 4-member Cargo workspace with a compiler-enforced acyclic dependency
direction syntax ← engine ← cli ← bin:
crates/
huck-syntax/src/ Shell-free front-end (no dependencies)
lexer.rs incremental Lexer: emits small atoms/word-parts; owns
Word/WordPart/SubscriptKind (with a parser-driven mode
stack; never forward-scans for a matching delimiter)
parser.rs the parser — pulls tokens from the Lexer and owns all
delimiter-matching/recursion; parse_sequence → Sequence
command.rs AST types (Sequence/Pipeline/Command/SimpleCommand/
ExecCommand/Redirect…) + assignment-word helpers
brace_expand.rs {a,b}/{1..N} brace expansion
generate.rs AST → source (for `type`, `declare -f`, …)
errors.rs, util.rs error Display + quoting helpers
huck-engine/src/ terminal-free execution core (depends on huck-syntax)
shell_state.rs Shell struct (env, vars, jobs, options); error_prefix
expand.rs word/parameter/command/brace/tilde/pathname expansion
param_expansion.rs ${…} modifiers, transforms, substitution
arith.rs $(( )) Pratt parser + evaluator
executor.rs fork/exec, pipes, redirects, job control, function calls
builtins.rs builtin dispatch (printf, set, declare, read, getopts…)
error_emit.rs the error-emitter family (sh_error!/sh_error_to!/emit_*)
glob_match.rs extglob + POSIX-class matcher (string + pathname)
completion*.rs tab-completion driver + complete/compgen spec model
jobs.rs, traps.rs JobTable + SIGCHLD reaping; trap/signal dispatch
engine.rs embeddable Engine/ExecBuilder API
huck-cli/src/ interactive REPL + rustyline adapters (depends on engine)
huck (root)/src/ thin binary: main.rs → huck_cli::run(args)
docs/
architecture.md module map, key types, pipeline, where-to-add cheatsheet
bash-divergences.md intentional (kept-by-design) divergences from bash;
actionable ones live in GitHub `divergence` issues
superpowers/specs/ design spec per iteration
superpowers/plans/ implementation plan per iteration
Each iteration follows the same loop:
- Take an issue → pick an open
divergenceissue, or open a new one to capture the work - Brainstorm → design spec in
docs/superpowers/specs/ - Plan → task-by-task plan in
docs/superpowers/plans/ - Implement task-by-task on a feature branch, with per-task spec-compliance and code-quality review
- Final review across the whole branch, then open a pull request
(
Closes #N) for the maintainer to review and merge tomain - Verify against bash via a per-feature
tests/scripts/*_diff_check.shharness; the merged PR closes itsdivergenceissue
Tests live alongside each module in #[cfg(test)] mod tests blocks, plus
binary-driven integration tests under tests/. Interactive features (tab
completion, history recall, Ctrl-C, job control) are covered by PTY-driven
suites (tests/pty_interactive.rs, etc.) using the expectrl crate; they skip
gracefully where no PTY is available.
rustyline— line editingregex—[[ =~ ]]matchingglob— pathname expansion (plain globs)signal-hook— SIGINT, SIGCHLDlibc—waitpid,setpgid,killpg,kill,tcsetpgrp,signalexpectrl— PTY-driven interactive tests (dev-dependency)
MIT — see LICENSE.