@aliou/sh
v0.1.0
Published
Shell parser/AST for POSIX/Bash/mksh/zsh (WIP)
Readme
@aliou/sh
TypeScript shell parser inspired by mvdan/sh. Parses POSIX/Bash shell commands into a typed AST.
Zero dependencies. Single exported function. ~28 KB bundled.
Usage
import { parse } from "@aliou/sh";
const { ast } = parse('echo "hello $USER" | grep hello');
// ast.type === "Program"
// ast.body[0].command.type === "Pipeline"The parser returns a Program node containing Statement nodes. Each statement wraps a Command, which is one of:
SimpleCommand-- words, assignments, redirectsPipeline,Logical(&&,||)IfClause,WhileClause,ForClause,SelectClause,CaseClauseFunctionDecl,Subshell,BlockTestClause([[ ]]),ArithCmd((( ))),CoprocClause,TimeClauseDeclClause(declare,local,export,readonly,typeset,nameref)LetClause(let),CStyleLoop(for (( ; ; )))
Words contain typed parts: Literal, SglQuoted, DblQuoted, ParamExp, CmdSubst, ArithExp, ProcSubst.
Example: extract command names
import { parse, type SimpleCommand } from "@aliou/sh";
function extractCommandNames(node: unknown): string[] {
if (!node || typeof node !== "object") return [];
const n = node as Record<string, unknown>;
const names: string[] = [];
if (n.type === "SimpleCommand") {
const cmd = n as unknown as SimpleCommand;
if (cmd.words?.length) {
const first = cmd.words[0];
if (first.parts.length === 1 && first.parts[0].type === "Literal") {
names.push(first.parts[0].value);
}
}
}
for (const val of Object.values(n)) {
if (Array.isArray(val)) {
for (const item of val) names.push(...extractCommandNames(item));
} else if (val && typeof val === "object") {
names.push(...extractCommandNames(val));
}
}
return names;
}
const { ast } = parse("grep -rn npm package.json | head -5");
extractCommandNames(ast); // ["grep", "head"]Supported syntax
- Simple commands, pipelines, logical operators (
&&,||) - Single and double quotes, parameter expansion (
$var,${var:-default}) - Command substitution (
$(cmd),`cmd`), arithmetic expansion ($((expr))) - Process substitution (
<(cmd),>(cmd)) - Heredocs (
<<,<<-), herestrings (<<<) - All redirect operators (
>,>>,<,>&,<&,<>,>|,&>,&>>) - Assignments (
FOO=bar cmd), append assignments (FOO+=bar) - Array expressions (
arr=(a b c),arr=([0]=x [1]=y)) - Declaration builtins as special forms (
declare,local,export,readonly,typeset,nameref) letexpressions (let i++ j=2)- Control flow:
if/elif/else/fi,while/until,for/in,for ((...)),select/in,case/esac - Functions (
foo() {},function foo {}) - Subshells
(), blocks{} [[ ]]test expressions,(( ))arithmetic commandscoproc,time, negation (!)- Comments (optionally preserved via
keepCommentsoption), backslash line continuations, background (&), semicolons
Install
pnpm add github:aliou/shDevelopment
Requires Nix (provides Node 22 and pnpm):
nix develop
pnpm install # install deps
pnpm test # run tests (vitest)
pnpm typecheck # tsc --noEmit
pnpm check # biome format + lint
pnpm build # rolldown + tsc declarationsGit hooks (via husky):
- pre-commit: staged file formatting/linting + typecheck
- pre-push: tests
Status
Work in progress. Covers the Bash subset needed for AST-based command analysis (command classification, variable mutation tracking, guardrail enforcement). Not yet a complete POSIX/Bash parser -- notably missing: position tracking in AST nodes, extended globbing, and full arithmetic expression parsing.
License
UNLICENSED
