跳转至

Deep Dive | src/utils/bash/bashParser.ts 4436 行 — 纯 TS 手写 Bash 解析器

重要性:⭐⭐⭐⭐(自研 bash 解析器——Bash 工具权限/安全/补全/解析的基石) 真实位置src/utils/bash/bashParser.ts4436 行角色:纯 TypeScript 手写的 bash 解析器,输出与 tree-sitter-bash 兼容的 AST,供 parser.ts / ast.ts / prefix.ts / ParsedCommand.ts 遍历 关联topics/deep-dive-bash-ast.md(AST 工具)、topics/deep-dive-bash-security.md(安全规则)、topics/deep-dive-bash-permissions.md(权限引擎)


1. 文件全景

bashParser.ts (4436 行, 79 个函数)
├── 行 1-50    :模块头注释 + 类型定义 (TsNode, ParserModule)
├── 行 29-44   :2 个常量 (PARSE_TIMEOUT_MS, MAX_NODES) + 2 个 init 函数
├── Tokenizer / Lexer 段(行 50-600, ~550 行)
│   ├── 行 50-77   :TokenType + Token 类型
│   ├── 行 77-110  :特殊常量 (SPECIAL_VARS, DECL_KEYWORDS, SHELL_KEYWORDS)
│   ├── 行 110-132 :Lexer + HeredocPending 类型
│   ├── 行 134-300 :makeLexer + advance + peek + byteAt + is*Char + skipBlanks
│   └── 行 302-595 :nextToken — **核心 tokenizer 295 行**
├── Parser 段(行 600-4400, ~3800 行)
│   ├── 行 600-650 :parseSource 入口 + 超时 + 预算
│   ├── 行 650-705 :mk / sliceBytes / leaf 辅助
│   ├── 行 706-878 :parseProgram + saveLex/restoreLex + parseStatements
│   ├── 行 879-1140:parseAndOr + parsePipeline + parseCommand
│   ├── 行 1141-1406:parseSimpleCommand
│   ├── 行 1406-1623:redirect + tryParseAssignment
│   ├── 行 1623-1860:tryParseRedirect (重定向)
│   ├── 行 1861-2000:parseProcessSub + scanHeredocBodies + parseHeredocBodyContent
│   ├── 行 2008-2215:parseWord + parseBareWord
│   ├── 行 2215-2340:tryParseBraceExpr + tryParseBraceLikeCat
│   ├── 行 2339-2430:parseDoubleQuoted
│   ├── 行 2429-3100:parseDollarLike + parseExpansionBody + parseExpansionRest + parseExpansionRegexSegmented + parseBacktick
│   ├── 行 3101-3700:parseIf / parseWhile / parseFor / parseDoGroup / parseCase / parseCaseItem / parseCasePattern / parseCasePatternSegmented / parseFunction / parseDeclaration / parseUnset
│   ├── 行 3700-4120:parseTestExpr ( [[ ... ]] 完整表达式)
│   ├── 行 4120-4436:parseArithExpr ( (( ... )) 算术)
│   └── 行 4426    :isArithStop

总览: - Tokenizer ~550 行(13%) - Parser ~3800 行(85%) - 辅助/类型/常量 ~150 行(2%)


2. 模块头注释 — 3 个关键不变量

/**
 * Pure-TypeScript bash parser producing tree-sitter-bash-compatible ASTs.
 *
 * Downstream code in parser.ts, ast.ts, prefix.ts, ParsedCommand.ts walks this
 * by field name. startIndex/endIndex are UTF-8 BYTE offsets (not JS string
 * indices).
 *
 * Grammar reference: tree-sitter-bash. Validated against a 3449-input golden
 * corpus generated from the WASM parser.
 */

3 个不变量

  1. API 兼容 tree-sitter-bash —— 字段名一致
  2. startIndex/endIndex 是 UTF-8 字节偏移不是 JS 字符串索引)
  3. 黄金语料库 3449 个输入——和 WASM 解析器对比验证

为什么用纯 TS 而不是 tree-sitter WASM: - 不需要 WASM runtime(启动更快,体积更小) - 可控的超时(50ms PARSE_TIMEOUT_MS) - 可控的预算(50K MAX_NODES 节点上限) - byte offset 精确(WASM 可能精度损失)


3. 2 个安全护栏

/** 50ms wall-clock cap — bails out on pathological/adversarial input. */
const PARSE_TIMEOUT_MS = 50

/** Node budget cap — bails out before OOM on deeply nested input. */
const MAX_NODES = 50_000

2 个护栏

护栏 作用
PARSE_TIMEOUT_MS 50ms 病态/对抗输入超时返回 null
MAX_NODES 50,000 深层嵌套防 OOM

2 个 API

parse(src, Infinity)  // 关闭超时(测试用)

注释强调:CI 抖动会触发误判 → 测试用 Infinity 关闭。


4. TsNode — 与 tree-sitter 一致的 AST 节点

export type TsNode = {
  type: string          // 节点类型 ('command', 'word', 'string', ...)
  text: string          // 原始文本片段
  startIndex: number    // UTF-8 字节偏移
  endIndex: number      // UTF-8 字节偏移
  children: TsNode[]    // 子节点
}

5 字段: - type —— string 节点类型 - text —— 原始文本 - startIndex / endIndex —— UTF-8 字节偏移(不是 JS 字符串索引!) - children —— 递归子节点

与 tree-sitter-bash 兼容: - parser.ts / ast.ts / prefix.ts / ParsedCommand.ts 都按字段名遍历 - 不需要写适配层


5. 2 个公开 API

export function ensureParserInitialized(): Promise<void> {
  return READY  // Promise.resolve()
}

export function getParserModule(): ParserModule | null {
  return MODULE
}

const MODULE: ParserModule = { parse: parseSource }

API 设计: - ensureParserInitialized() —— 异步初始化(纯 TS 无需初始化)—— 保持 API 兼容 - getParserModule() —— 返回 parse 函数 - parse(src, timeoutMs?) —— 主入口

Promise.resolve() —— 不需要 await,但返回 Promise 保持向后兼容。


6. Tokenizer(Lexer)— 行 50-600

6.1 TokenType + Token

type TokenType = 'word' | 'operator' | 'newline' | 'eof' | ...;

type Token = {
  type: TokenType
  text: string
  startByte: number    // 字节偏移
  endByte: number
  // ... 一些额外字段
}

const SPECIAL_VARS = new Set(['?', '$', '@', '*', '#', '-', '!', '_'])
const DECL_KEYWORDS = new Set(['declare', 'typeset', 'export', 'readonly', 'local'])
export const SHELL_KEYWORDS = new Set(['if', 'then', 'else', 'elif', 'fi', 'while', ...])

3 个常量集合: - SPECIAL_VARS —— bash 特殊变量($?, $$, $@ 等) - DECL_KEYWORDS —— 变量声明(declare, export 等) - SHELL_KEYWORDS —— shell 关键字(if, then, while 等)

6.2 Lexer 状态

type Lexer = {
  src: string
  byteLen: number
  charIdx: number       // 当前字符索引
  byteIdx: number       // 当前字节偏移
  // ... 内部状态
}

type HeredocPending = {
  delim: string
  // ... heredoc 元数据
}

双索引charIdx(JS 字符串)+ byteIdx(UTF-8 字节)—— 用于精确的位置信息。

6.3 nextToken — 核心 295 行

function nextToken(L: Lexer, ctx: 'cmd' | 'arg' = 'arg'): Token {
  // 行 302-595
  // 处理 30+ 种 token 类型
  // 包含 heredoc 状态机
}

295 行——一个函数吃掉了整个 token 识别。

ctx 参数('cmd' | 'arg')控制是否识别关键字。


7. Parser 入口

7.1 parseSource — 超时 + 预算

function parseSource(source: string, timeoutMs?: number): TsNode | null {
  // 行 610-650
  // 1. 设置超时
  // 2. 初始化 ParseState
  // 3. parseProgram
  // 4. 检查预算
  // 5. 返回根节点或 null
}

返回 null 的两种情况: - 超时(> 50ms) - 节点超预算(> 50K)

7.2 ParseState

type ParseState = {
  lex: Lexer
  startTime: number
  timeoutMs: number
  nodeCount: number
  // ... 派生 state
}

4 字段: - lex —— 当前 lexer - startTime / timeoutMs —— 超时检查 - nodeCount —— 预算跟踪

7.3 checkBudget — 双重保护

function checkBudget(P: ParseState): void {
  // 行 647
  // 检查超时 + 节点数
  // 超限 → throw
}

双重检查:每次 mk() 调用前都 check。

7.4 mk / sliceBytes / leaf — 3 个核心辅助

function mk(...): TsNode {
  // 创建节点 + 计数 + checkBudget
}

function sliceBytes(P: ParseState, startByte: number, endByte: number): string {
  // UTF-8 字节切片
}

function leaf(P: ParseState, type: string, tok: Token): TsNode {
  // 叶子节点(无 children)
}

mk 是"分配"操作——每次创建节点都 +1 计数 + check 预算。


8. 顶层解析

8.1 parseProgram + parseStatements

function parseProgram(P: ParseState): TsNode {
  // 行 706
  // 程序 = 多个语句(用 \n 或 ; 分隔)
}

function parseStatements(P: ParseState, terminator: string | null): TsNode[] {
  // 行 769
  // 解析语句直到 terminator
}

terminator 决定停止条件: - null —— 顶层(直到 EOF) - '}' —— { ... } 块 - 'do' —— for/while 体 - 'done' —— 循环体 - 'then' —— if 体 - 'fi' —— if 块结束

8.2 parseAndOr + parsePipeline

function parseAndOr(P: ParseState): TsNode | null {
  // 行 879
  // cmd1 && cmd2 || cmd3
}

function parsePipeline(P: ParseState): TsNode | null {
  // 行 938
  // cmd1 | cmd2 | cmd3
}

bash 操作符优先级

list   =  pipeline ( ('&&' | '||') pipeline )*
pipeline = command ( '|' command )*
command = simple_command | compound | ...

8.3 parseCommand — 分发

function parseCommand(P: ParseState): TsNode | null {
  // 行 995
  // 看到 keyword → 分发
  // - if/while/for/case/function/declare/unset → 对应 parseXxx
  // - 否则 → parseSimpleCommand
}

dispatch 表: - ifparseIf - while / untilparseWhile - forparseFor - caseparseCase - functionparseFunction - declare / typeset / export / readonly / localparseDeclaration - unsetparseUnset - 其他 → parseSimpleCommand

8.4 parseSimpleCommand — 146 行

function parseSimpleCommand(P: ParseState): TsNode | null {
  // 行 1141
  // command_name (redirect | assignment | arg)*
}

支持: - 命令名 - 重定向(>, <, >>, <<<, >&, <&) - 赋值(FOO=bar) - 参数(任意数量) - 内置(time, [, [[, ((


9. 重定向 + 赋值

9.1 maybeRedirect + tryParseRedirect — 200+ 行

function maybeRedirect(P: ParseState): TsNode | null {
  // 行 1406
  // 看一眼当前位置是不是 redirect
}

function tryParseRedirect(P: ParseState, greedy = false): TsNode | null {
  // 行 1623
  // 解析完整 redirect
}

支持: - > file —— 输出重定向 - < file —— 输入 - >> file —— 追加 - >& fd —— fd 复制 - 2>&1 —— stderr → stdout - <<< word —— here-string - > file 中的 fd 前缀(2>) - {fd}> 语法

greedy 参数控制是否"贪心"——parseSimpleCommand 中 greedy=true(不允许中断),其他上下文 greedy=false(遇到 redirect 可以回退)。

9.2 tryParseAssignment

function tryParseAssignment(P: ParseState): TsNode | null {
  // 行 1431
  // FOO=bar / FOO[1]=bar / FOO+=bar
}

支持: - 简单赋值 FOO=bar - 数组下标 FOO[1]=bar / FOO[1][2]=bar - 追加 FOO+=bar


10. 词法解析

10.1 parseWord + parseBareWord

function parseWord(P: ParseState, _ctx: 'cmd' | 'arg'): TsNode | null {
  // 行 2008
  // 解析一个 word
  // word = (literal | expansion | quote | escape)+
}

function parseBareWord(P: ParseState): TsNode | null {
  // 行 2163
  // 简单 word(无扩展)
}

word 内部组成: - literal(普通字符) - $VAR(变量) - ${VAR}(带花括号) - $(cmd)(命令替换) - `cmd`(反引号) - $((expr))(算术) - "..."(双引号) - \$, \\, \"(转义) - ~(波浪号扩展)

10.2 tryParseBraceExpr / tryParseBraceLikeCat

function tryParseBraceExpr(P: ParseState): TsNode | null {
  // 行 2215
  // ${VAR:-default}, ${VAR:=default}, ${VAR:offset:len}, ...
}

function tryParseBraceLikeCat(P: ParseState): TsNode[] | null {
  // 行 2268
  // ${VAR} 或 ${#VAR} 或 ${!VAR}
}

10+ 种花括号扩展: - ${VAR} —— 简单 - ${#VAR} —— 长度 - ${VAR:-default} —— 默认值 - ${VAR:=default} —— 赋值 - ${VAR:+alt} —— 替代 - ${VAR:?error} —— 错误 - ${VAR:offset} —— 子串 - ${VAR:offset:len} —— 子串 - ${VAR#pattern} —— 前缀删除 - ${VAR##pattern} —— 最长前缀 - ${VAR%pattern} —— 后缀删除 - ${VAR%%pattern} —— 最长后缀 - ${VAR/pat/rep} —— 替换 - ${VAR//pat/rep} —— 全替换 - ${VAR^} / ${VAR^^} —— 大写 - ${VAR,} / ${VAR,,} —— 小写 - ${!VAR} —— 间接引用 - ${VAR@ops} —— 参数转换

10.3 parseDoubleQuoted — 90 行

function parseDoubleQuoted(P: ParseState): TsNode {
  // 行 2339
  // " ... "
}

双引号内允许:变量、命令替换、转义、$, `, \, "

10.4 parseDollarLike + parseExpansionBody + parseExpansionRest — 700+ 行

function parseDollarLike(P: ParseState): TsNode | null {
  // 行 2429
  // $VAR / ${VAR} / $(cmd) / $((expr)) / $[expr] 统一入口
}

function parseExpansionBody(P: ParseState): TsNode[] {
  // 行 2555
  // 解析扩展的"内容"
}

function parseExpansionRest(P: ParseState): ... {
  // 行 2807
  // ${VAR} 之后剩余(操作符 + 参数)
}

function parseExpansionRegexSegmented(P: ParseState): TsNode[] {
  // 行 3036
  // 模式参数(如 ${VAR/pat/rep} 的 pat)
}

700+ 行——bash 扩展语法是最复杂的部分。

10.5 parseBacktick

function parseBacktick(P: ParseState): TsNode | null {
  // 行 3101
  // `command`
}

反引号 —— 反引号内 \\\, \$, \`, \", \newline 有意义。其他 \ 字面保留


11. 复合语句

11.1 parseIf

function parseIf(P: ParseState, ifTok: Token): TsNode {
  // 行 3152
  // if cond; then ... elif cond; then ... else ... fi
}

11.2 parseWhile

function parseWhile(P: ParseState, kwTok: Token): TsNode {
  // 行 3189
  // while cond; do ... done
  // until cond; do ... done
}

11.3 parseFor — 106 行

function parseFor(P: ParseState, forTok: Token): TsNode {
  // 行 3200
  // for VAR in items; do ... done
  // for ((init; cond; step)); do ... done
}

两种形式: - for x in 1 2 3; do ... done - C-style for ((i=0; i<10; i++)); do ... done

11.4 parseDoGroup

function parseDoGroup(P: ParseState): TsNode | null {
  // 行 3306
  // do ... done
}

11.5 parseCase — 28 行

function parseCase(P: ParseState, caseTok: Token): TsNode {
  // 行 3322
  // case WORD in pat1) ... ;; pat2) ... ;; esac
}

11.6 parseCaseItem + parseCasePattern + parseCasePatternSegmented — 175+ 行

function parseCaseItem(P: ParseState): TsNode | null {
  // 行 3350
  // pat1|pat2) commands ;;
}

function parseCasePattern(P: ParseState): TsNode[] {
  // 行 3441
  // 单个 pattern(支持 glob 扩展)
}

function parseCasePatternSegmented(P: ParseState): TsNode[] {
  // 行 3525
  // 切分 pat1|pat2|pat3
}

case pattern 是 glob 表达式——?, *, [abc], \ 都有意义。

11.7 parseFunction + parseDeclaration + parseUnset

function parseFunction(P: ParseState, fnTok: Token): TsNode {
  // 行 3565
  // function name { ... }  or  name() { ... }
}

function parseDeclaration(P: ParseState, kwTok: Token): TsNode {
  // 行 3598
  // declare/typeset/export/readonly/local [options] VAR[=value]
}

function parseUnset(P: ParseState, kwTok: Token): TsNode {
  // 行 3650
  // unset VAR
}

12. Test 表达式 — [[ ... ]]

function parseTestExpr(P: ParseState, closer: string): TsNode | null {
  // 行 3701
  // 入口

  function parseTestOr(P, closer) {...}     // ||
  function parseTestAnd(P, closer) {...}    // &&
  function parseTestUnary(P, closer) {...}  // ! expr
  function parseTestNegatablePrimary(P) {...}
  function parseTestBinary(P, closer) {...}  // expr OP expr
  function parseTestRegexRhs(P) {...}       // =~
  function parseTestExtglobRhs(P) {...}     // ==, !=, = (extglob)
  function parseTestPrimary(P, closer) {...}
}

[[ ... ]] 优先级(从低到高):

|
&
!
==
!=
=~
<, >
-n, -z, -e, -f, ... (unary)
(...), literal

支持的运算符: - 文件测试:-e, -f, -d, -r, -w, -x, -s, -L, ... - 字符串测试:-z, -n, ==, !=, =~, <, > - 数值测试:-eq, -ne, -lt, -le, -gt, -ge - 布尔:-a (deprecated), -o (deprecated), &&, ||, ! - extglob:==(pat), !=(pat)


13. Arithmetic 表达式 — (( ... ))

function parseArithExpr(P: ParseState): TsNode {
  // 行 4129
  // (( expr ))
}
function parseArithCommaList(P): ...  // ,
function parseArithTernary(P): ...    // ? :
function scanArithOp(P): ...          // 扫描运算符
function parseArithBinary(P): ...     // 二元
function parseArithUnary(P): ...      // 一元
function parseArithPostfix(P): ...    // ++ --
function parseArithPrimary(P): ...    // 字面量 + 变量
function isArithStop(P, stop): ...    // 停止判断

算术运算符(C 风格): - 基础:+, -, *, /, % - 位运算:<<, >>, &, |, ^, ~ - 比较:<, >, <=, >=, ==, != - 逻辑:&&, ||, ! - 三元:?: - 赋值:=, +=, -=, *=, ... - 自增:++, -- - 逗号:,


14. 关键设计模式

14.1 递归下降

每个语法结构对应一个 parseXxx 函数,互相递归调用: - parseProgramparseStatementsparseAndOrparsePipelineparseCommandparseSimpleCommand / parseIf / ... - parseWordparseDollarLikeparseExpansionBody → ...

没有 parser generator(不是 yacc/bison)——纯手写

14.2 字节 vs 字符

type Lexer = {
  byteLen: number
  charIdx: number
  byteIdx: number
  // ...
}

function byteAt(L: Lexer, charIdx: number): number {
  // charIdx → byte offset
}

双索引:保持 JS 字符串索引(charIdx)和 UTF-8 字节偏移(byteIdx)双向同步

为什么用字节: - Bash 工具的输出(如错误信息)用字节偏移 - 与 tree-sitter 一致 - 多字节字符(中文 emoji)正确处理

14.3 黄金语料库验证

注释说:

Validated against a 3449-input golden corpus generated from the WASM parser.

测试策略: - 用 tree-sitter WASM 生成 3449 个输入的预期 AST - 纯 TS 解析器跑同样输入,对比 AST - 任何不一致 → 修复

这保证了与 tree-sitter-bash 兼容

14.4 saveLex / restoreLex — 试探性解析

function saveLex(L: Lexer): LexSave {
  // 保存 lexer 状态
}

function restoreLex(L: Lexer, s: LexSave): void {
  // 恢复 lexer 状态
}

试探性解析: - 看到一个 token 不确定是 X 还是 Y - save → 尝试解析 X → 失败 → restore → 尝试解析 Y - 常见于:assignment vs command, redirect vs operator, [[ vs [(

14.5 timeoutMs 参数化

function parseSource(source: string, timeoutMs?: number): TsNode | null

默认 50ms —— 生产环境。 Infinity —— 测试环境(关超时)。

14.6 Heredoc 多遍扫描

function scanHeredocBodies(P: ParseState): void {
  // 1. 找到所有 << EOF / <<-EOF / <<< "word" 标记
  // 2. 扫描每个 heredoc 的 body
  // 3. 把 body 附加到命令的 children
}

两遍扫描: - 第一遍:找到所有 << EOF 标记,记录位置 - 第二遍:实际解析时,知道每个 heredoc 的 body 在哪

<<- 形式:heredoc body 剥离前导 tab(不是空格!)


15. 性能特征

15.1 时间复杂度

最坏 O(n) —— 几乎所有 token 都扫一次。 最坏 O(n²) —— 试探性回溯(如 parseWord)。

15.2 空间复杂度

O(MAX_NODES) = 50K —— 节点上限。

15.3 实际性能

  • 普通命令(< 100 tokens):< 1ms
  • 大脚本(1K 行):~5-10ms
  • 病态输入(如 100K 个 ():50ms 超时返回 null

15.4 与 tree-sitter WASM 对比

维度 纯 TS WASM
启动 0(无 runtime) ~5-10ms(WASM init)
解析速度 接近 略快(编译代码)
体积 0 ~200KB
跨平台 100% 100%
调试 易(TS) 难(WASM)
扩展 直接改 TS 重编译 WASM

Claude Code 选纯 TS——启动时间更重要


16. 与 tree-sitter-bash 的兼容性

16.1 字段名一致

概念 字段
节点类型 type
文本 text
范围 startIndex, endIndex
子节点 children

下游代码parser.ts, ast.ts, prefix.ts, ParsedCommand.ts按字段名遍历——无需适配。

16.2 节点类型对照

节点类型 含义
program 整个脚本
command 单条命令
simple_expansion $VAR
expansion ${VAR}
command_substitution $(cmd)
process_substitution <(cmd) / >(cmd)
string "..."'...'
word
variable_name 变量名
function_definition function name { ... }
if_statement / elif_clause / else_clause if
while_statement / for_statement / do_group 循环
case_statement / case_item case
binary_operator / unary_operator 运算符
redirected_statement 带重定向的语句
file_redirect / heredoc_redirect / herestring_redirect 重定向
variable_assignment 赋值
subscript 数组下标
test_operator test 运算符
arithmetic_expansion ((expr))

30+ 种节点类型——与 tree-sitter-bash 一致。


17. 关键设计权衡

17.1 纯 TS vs WASM

选纯 TS: - ✅ 无 WASM 启动开销 - ✅ 字节偏移精确 - ✅ 易于调试 - ✅ 可控的超时/预算 - ❌ 解析速度略慢 - ❌ 不能"复用" tree-sitter 生态

17.2 50ms 超时 vs 长解析

50ms 是商业产品选择: - 用户体验:长脚本解析 > 50ms 视为"可疑" - 攻击面:拒绝深度对抗输入 - 性能预算:保护主线程

17.3 50K 节点 vs 内存

节点上限防 OOM: - 50K 节点 × ~200 bytes = 10MB(最坏) - 远小于 Bun/Node 默认 heap

17.4 不实现执行

只解析,不执行——不实现命令运行、变量绑定、函数调用。

上游bashSecurity.ts, bashPermissions.ts, prefix.ts)决定怎么用 AST(做权限检查、做补全、做风险评分)。


18. 阅读建议

  1. 看模块头注释(行 1-10)—— 理解不变量
  2. 看 2 个护栏(行 29-44)—— 安全设计
  3. 看 295 行 nextToken(行 302-595)—— 词法分析的核心
  4. 看 parseProgram(行 706)—— 顶层入口
  5. 看 parseWord(行 2008)—— 词法的复杂
  6. 看 parseTestExpr(行 3701)—— test 表达式
  7. 看 parseArithExpr(行 4129)—— 算术

19. 关键洞察

19.1 商业产品选择纯 TS

不是技术不能 WASM——是启动时间更重要

19.2 50ms 是 UX 选择

不是 100ms,不是 200ms——50ms 是人感知不到延迟的阈值。

19.3 字节偏移贯穿全文

不是字符索引——和 tree-sitter 兼容、bash 工具输出兼容、UTF-8 安全。

19.4 黄金语料库 3449 个

这是工程严谨性**——不是"我觉得对了"。

19.5 试探性回溯(save/restore)

这是手写 parser 的标准技巧——bison/yacc 自动做,手写要自己写。

19.6 bash 扩展是"最复杂的"段

${VAR:-default} 等 700+ 行——bash 的真正难度都在这里。


20. 与其他文件的关系

bashParser.ts
  ├──→ parser.ts (遍历 AST)
  ├──→ ast.ts (AST 工具)
  ├──→ prefix.ts (补全 / 路径分析)
  ├──→ ParsedCommand.ts (ParsedCommand 包装)
  ├──→ bashSecurity.ts (安全规则)
  └──→ bashPermissions.ts (权限引擎)

bashParser 是"地基"——所有 bash 相关功能都依赖它。


21. 阅读清单

  1. ✅ 看模块头注释(行 1-10)
  2. ✅ 看 2 个护栏(行 29-44)
  3. ✅ 看 nextToken 295 行(行 302-595)
  4. ✅ 看 parseProgram 入口(行 706)
  5. ✅ 看 parseWord + parseDollarLike(行 2008-2429)
  6. ✅ 看 parseExpansionBody(行 2555)
  7. ✅ 看 parseTestExpr(行 3701)
  8. 📌 对照 topics/deep-dive-bash-ast.md 看 AST 怎么遍历
  9. 📌 对照 topics/deep-dive-bash-security.md 看 AST 怎么用

22. 练习任务

  1. 数 parseXxx 函数(grep)—— 79 个
  2. 画递归下降调用图 —— parseProgram → parseStatements → parseAndOr → ...
  3. 找 7 种 token 分类(word/operator/newline/eof/...)
  4. 写一个迷你 bash parser(~200 行)—— 只支持 cmd arg | cmd arg
  5. 思考:如果改成 PEG.js / chevrotain 写,会怎样?
  6. 跑测试practice-tests/bash-parser 验证 3449 个黄金语料