
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
Fast 0-deps bash parser written in TypeScript
npm install unbash
import { parse } from "unbash";
const ast = parse('if [ -f "$1" ]; then cat "$1"; fi');
Result:
{
type: "Script",
commands: [{
type: "If",
clause: { type: "Command", name: { text: "[" }, ... },
then: { type: "Command", name: { text: "cat" }, ... }
}]
}
Every node carries pos/end as absolute offsets into the original source, so source.slice(node.pos, node.end) yields that node's text at any nesting depth — including inside command, process, arithmetic, and ${ } substitutions, and parameter-expansion sub-fields. No need to track inner offsets while traversing.
const source = 'echo "$(date -u) $(whoami)"';
const ast = parse(source);
const sub = ast.commands[0].command.suffix[0].parts[0].parts[0]; // the $(date -u)
source.slice(sub.script.pos, sub.script.end); // → "date -u"
parts is computed lazily — accessing it resolves a word's substitutions in place. Every word's text equals its source span (source.slice(pos, end)); value carries the interpreted form (quotes resolved).
The one exception to absolute offsets: legacy escaped backticks (`… \`…\` …`) rebuild their inner with the escapes removed, so it is no longer a verbatim substring of the source — the nested script's offsets stay relative to that rebuilt inner.
Basic opinionated printer, does not preserve whitespace or comments (except shebang):
import { parse } from "unbash";
import { print } from "unbash/print";
const ast = parse('if [ -f "$1" ]; then cat "$1"; fi');
const script = print(ast);
Result:
if [ -f "$1" ]; then
cat "$1"
fi
tree-sitter-bash is an excellent choice if you need:
ERROR nodes and continues parsingunbash might be a good fit if you prefer:
${ cmd; }, [[ ]], (( )), and extglobsh-syntax is a WASM wrapper around the robust mvdan/sh Go parser. It is highly recommended if you need:
print)unbash might be a good fit if you prefer:
bash-parser (last publish: 2017) and its fork @ericcornelissen/bash-parser (community dependency maintenance fork ❤️ now archived) might be interesting if you need:
unbash might be a good fit if you prefer:
[[ ]] test expressionsselect, process substitution, etc. etc.)Relative performance comparison (on Apple M1 Pro/32GB), unbash is x times faster:
| Parser | short | advanced | medium | large |
|---|---|---|---|---|
| tree-sitter-bash (native) | 13x | 9x | 4x | 5x |
| tree-sitter-bash (WASM) | 16x | 12x | 8x | 8x |
| sh-syntax | 2136x | 1537x | 8x | 4x |
| bash-parser | 256x | N/A | N/A | N/A |
| @ericcornelissen/bash-parser | 267x | N/A | N/A | N/A |
Run the benchmarks using Node.js v22:
pnpm install
node bench/all.ts
unbash is 53K minified, 13KB gzipped.
ISC
FAQs
Fast 0-deps bash parser written in TypeScript
The npm package unbash receives a total of 8,050,409 weekly downloads. As such, unbash popularity was classified as popular.
We found that unbash demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.