(((( )))) The wind blows where it will--
(((( )))) you hear its sound, but you
(((( P A R A B L E )))) don't know where it's from
)))) (((( or where it's going.
)))) (((( โ John 3:8
Parse bash exactly as bash does. One file, zero dependencies, in your language. This is the only complete bash parser for most languages. Extensively validated against bash itself.
LLM-driven development. This project is an exercise in maximizing what LLMs can do. An 11,000-line recursive descent parser for one of the gnarliest grammars in computing, plus a custom multi-target transpiler, built and maintained almost entirely through AI assistanceโit wouldn't exist without them.
Match bash exactly. Bash is the oracle. We patched GNU Bash 5.3 so it reveals its internal parse tree, then test against it. No spec interpretation, no "close enough"โif bash parses it one way, so do we. Bash always tells the truth, even when it's lying.
Portable performance. Hand-written recursive descentโno generators, no native extensions, no imports. Tongues, our custom-built transpiler, builds release binaries in many languages. All run the same tests.
All releases, even Python, run through the transpiler. Current languages:
| Language | Min Version | v0.1.0 |
|---|---|---|
| Javascript | Node.js 21 | parable.js |
| Python | CPython/Pypy 3.12 | parable.py |
Caveats: output code quality is a work in progress. Currently the transpiler prioritizes correctness over readability; generated code may not yet match hand-written idioms, yet. Additionally, the project's fuzzer still finds obscure edge cases where Parable differs from GNU Bash. While Parable matches bash for essentially 100% of real world examples, we're still shaking out remaining theoretical differences.
Bash's grammar is notoriously irregular. Existing tools make tradeoffs:
- bashlex โ Incomplete. Fails on heredocs, arrays, arithmetic, and more. Fine for simple scripts, breaks on real ones.
- Oils/OSH โ A whole shell, not an embeddable library. Makes intentional parsing tradeoffs for a cleaner languageโfine for their goals, but won't predict what real bash does.
- tree-sitter-bash โ Editor-focused, not Python-native. Many open parsing bugs.
- mvdan/sh โ Go-native, but doesn't fully match bash. Targets POSIX with bash extensions.
- sh-syntax โ WASM port of mvdan/sh, not pure JS. Inherits the same limitations.
Parable is the only library in these languages that parses bash exactly as bash doesโtested against bash's own AST. For security and sandboxing, 95% coverage is 100% inadequate.
Use cases:
- Security auditing โ Analyze scripts for command injection, dangerous patterns, or policy violations. The construct you can't parse is the one that owns you.
- CI/CD analysis โ Understand what shell scripts actually do before running them.
- Migration tooling โ Convert bash to other languages with full AST access.
- Linting and formatting โ Build bash linters in Python & JS without regex hacks.
The dark corners of bash that break other parsers:
# Nested everything
echo $(cat <(grep ${pattern:-".*"} "${files[@]}"))
# Heredoc inside command substitution inside heredoc
cat <<OUTER
$(cat <<INNER
$nested
INNER
)
OUTER
# Multiple heredocs on one line
diff <(cat <<A
one
A
) <(cat <<B
two
B
)
# Quoting transforms on array slices
printf '%q\n' "${arr[@]:2:5@Q}"
# Regex with expansions in conditional
[[ ${foo:-$(whoami)} =~ ^(user|${pattern})$ ]]
# Process substitution as redirect target
cmd > >(tee log.txt) 2> >(tee err.txt >&2)
# Extglob patterns that look like syntax
case $x in @(foo|bar|?(baz))) echo match;; esacThe full grammarโparameter expansion, heredocs, process substitution, arithmetic, arrays, conditionals, coprocesses, all of it.
Parable is designed for tools that need to predict what bash will do. Honest caveats:
- Tested, not mathematically proven. We validate against bash's AST for thousands of difficult edge cases, but this is not a formal proof, verified by a proof checker. A determined attacker with capable LLMs may find discrepancies.
- Validated against bash 5.3. Core parsing is stable across versions, but edge cases may differ. If your target runs ancient bash (macOS ships 3.2) or relies on version-specific quirks, verify independently.
- Bash wasn't built for this. Even perfect parsing doesn't guarantee predictable execution.
shoptsettings, aliases, and runtime context all affect behavior. True security means containers or VMs.
Parable strives to be the best available tool for static bash analysisโoracle-tested, not spec-interpreted. But for high-stakes security, nothing replaces defense in depth.
Every test validated against real bash 5.3 ASTs.
- GNU Bash test corpus: 19,370 lines
- Oils bash corpus: 2,495 tests
- tree-sitter-bash corpus: 125 tests
- Parable hand-written tests: 1,900+ tests
from parable import parse
# Returns an AST, not string manipulation
ast = parse("ps aux | grep python | awk '{print $2}'")
# S-expression output for inspection
print(ast[0].to_sexp())
# (pipe (command (word "ps") (word "aux")) (pipe (command (word "grep") (word "python")) (command (word "awk") (word "'{print $2}'"))))
# Handles the weird stuff
ast = parse("cat <<'EOF'\nheredoc content\nEOF")
print(ast[0].to_sexp())
# (command (word "cat") (redirect "<<" "heredoc content\n"))src/
โโโ parable.py # Single-file Python parser
tests/
โโโ bin/ # Test runners + corpus utilities
โโโ parable/ # Parable test cases
โโโ corpus/ # Validation corpus
tools/
โโโ fuzzer/ # Differential fuzzer
MIT