Skip to content

jdstanhope/huck

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2,410 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

huck

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.

Installation

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

Status

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-targets clean.
  • 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 ls wall-clock now matches bash.
  • Sources ~/.bashrc-class startup files and the system bash-completion framework without errors.
  • Known gaps and actionable divergences are tracked as GitHub issues labelled divergence; deliberate, kept-by-design divergences live in docs/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/.

Build and run

cargo build --release
cargo run                # interactive REPL
cargo test               # full test suite

What huck supports

Command 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-style for ((;;)) — 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:]], and extglob (?(…)/*(…)/+(…)/ @(…)/!(…)) 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.

Known differences from bash

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.

Project layout

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

Development workflow

Each iteration follows the same loop:

  1. Take an issue → pick an open divergence issue, or open a new one to capture the work
  2. Brainstorm → design spec in docs/superpowers/specs/
  3. Plan → task-by-task plan in docs/superpowers/plans/
  4. Implement task-by-task on a feature branch, with per-task spec-compliance and code-quality review
  5. Final review across the whole branch, then open a pull request (Closes #N) for the maintainer to review and merge to main
  6. Verify against bash via a per-feature tests/scripts/*_diff_check.sh harness; the merged PR closes its divergence issue

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.

Dependencies

  • rustyline — line editing
  • regex[[ =~ ]] matching
  • glob — pathname expansion (plain globs)
  • signal-hook — SIGINT, SIGCHLD
  • libcwaitpid, setpgid, killpg, kill, tcsetpgrp, signal
  • expectrl — PTY-driven interactive tests (dev-dependency)

License

MIT — see LICENSE.

About

A Bash compatible shell written in Rust by Claude

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors