|
| 1 | +--- |
| 2 | +name: awk-posix-compat |
| 3 | +description: | |
| 4 | + Shell 脚本中 awk 的 POSIX 兼容性指南。 |
| 5 | + Use when: 编写或审查包含 awk 的 shell 脚本, |
| 6 | + 尤其是需要 macOS + Linux 跨平台运行的场景。 |
| 7 | + 触发词: awk, BSD awk, POSIX regex, [[:space:]], |
| 8 | + guard 脚本, 跨平台 shell |
| 9 | +author: Claude Code |
| 10 | +version: 1.0.0 |
| 11 | +date: 2026-04-03 |
| 12 | +--- |
| 13 | + |
| 14 | +# awk POSIX 兼容性指南 |
| 15 | + |
| 16 | +## Problem |
| 17 | + |
| 18 | +macOS 自带 BSD awk 严格遵循 POSIX 标准,不支持 GNU awk (gawk) 的正则扩展。 |
| 19 | +在 Linux (gawk) 开发、macOS (BSD awk) 运行时**静默匹配失败** — 无报错但结果为空。 |
| 20 | + |
| 21 | +这类 bug 极难调试:脚本不报错,只是检测不到任何匹配,看起来像"没有问题"。 |
| 22 | + |
| 23 | +## Context / Trigger Conditions |
| 24 | + |
| 25 | +- 编写包含 `awk '...'` 的 shell 脚本 |
| 26 | +- 脚本需要在 macOS + Linux 上运行 |
| 27 | +- 使用 awk 正则做代码模式检测(guard/linter/hook) |
| 28 | +- 错误现象:Linux 上能检测到问题,macOS 上检测为 0 |
| 29 | + |
| 30 | +## Solution |
| 31 | + |
| 32 | +### 1. 正则替换规则(6 条) |
| 33 | + |
| 34 | +| GNU awk 扩展 | POSIX 替代 | 说明 | |
| 35 | +|-------------|------------|------| |
| 36 | +| `\s` | `[[:space:]]` | 空白字符 | |
| 37 | +| `\S` | `[^[:space:]]` | 非空白字符 | |
| 38 | +| `\d` | `[0-9]` | 数字 | |
| 39 | +| `\w` | `[A-Za-z0-9_]` | 单词字符 | |
| 40 | +| `\b` | `[[:<:]]` / `[[:>:]]` | 词边界(BSD 语法) | |
| 41 | +| `\+` | `{1,}` 或重写模式 | 一次或多次 | |
| 42 | + |
| 43 | +```bash |
| 44 | +# ❌ BAD — gawk 扩展,BSD awk 静默失败 |
| 45 | +awk '/\s+defer\s/' "$file" |
| 46 | + |
| 47 | +# ✅ GOOD — POSIX 兼容 |
| 48 | +awk '/[[:space:]]+defer[[:space:]]/' "$file" |
| 49 | +``` |
| 50 | + |
| 51 | +### 2. 字符级计数(gsub 法则) |
| 52 | + |
| 53 | +禁止行级正则匹配计数大括号: |
| 54 | + |
| 55 | +```bash |
| 56 | +# ❌ BAD — 单行多个 { 时只计 1 次 |
| 57 | +awk '/\{/ { depth++ }' "$file" |
| 58 | + |
| 59 | +# ✅ GOOD — gsub 返回替换次数 = 精确字符数 |
| 60 | +awk '{ |
| 61 | + tmp = $0; opens = gsub(/\{/, "", tmp) |
| 62 | + tmp = $0; closes = gsub(/\}/, "", tmp) |
| 63 | + depth += opens - closes |
| 64 | +}' "$file" |
| 65 | +``` |
| 66 | + |
| 67 | +**原因**:`/{/` 是行级匹配 — 一行有 `func() { if x {` 两个 `{`,行级只计 1 次,导致 depth 跟踪错误,后续作用域判断全部偏移。 |
| 68 | + |
| 69 | +### 3. 作用域隔离(嵌套结构检测) |
| 70 | + |
| 71 | +检测"X 在 Y 内"的模式(如 `defer` 在 `for` 循环内)时,需要区分语法层级: |
| 72 | + |
| 73 | +```bash |
| 74 | +# 需要 flit_depth 变量跟踪 func literal 嵌套 |
| 75 | +# go func() { defer f.Close() }() 在 for 循环内是安全的 |
| 76 | +# 因为 defer 绑定到 func literal 而非外层 for |
| 77 | + |
| 78 | +if (is_func_literal && loop_depth > 0) { |
| 79 | + flit_depth++ |
| 80 | + flit_base[flit_depth] = total_depth # 记录进入时的 brace 深度 |
| 81 | +} |
| 82 | + |
| 83 | +# defer 只在 loop_depth > 0 且 flit_depth == 0 时才报警 |
| 84 | +``` |
| 85 | +
|
| 86 | +## Verification |
| 87 | +
|
| 88 | +```bash |
| 89 | +# 1. 运行 portability check |
| 90 | +bash scripts/setup/check.sh |
| 91 | +# → "Guard Script Portability" 段应全绿 |
| 92 | + |
| 93 | +# 2. 手动扫描违规 |
| 94 | +find guards/ -name '*.sh' -print0 \ |
| 95 | + | xargs -0 grep -rnE '/[^/"]*\\[sdwb]' \ |
| 96 | + | grep -vE '^\s*#|grep |sed ' |
| 97 | +# → 期望输出为空(0 violations) |
| 98 | +``` |
| 99 | +
|
| 100 | +## Example |
| 101 | +
|
| 102 | +**场景**:`check_defer_in_loop.sh` 在 macOS 上检测不到任何 defer-in-loop 问题 |
| 103 | +
|
| 104 | +**Before**(3 个 bug): |
| 105 | +```awk |
| 106 | +# Bug 1: \s 在 BSD awk 静默失败 |
| 107 | +if (match(line, /^\s*for(\s|$)/)) |
| 108 | + |
| 109 | +# Bug 2: 行级计数漏计 |
| 110 | +/\{/ { total_depth++ } |
| 111 | + |
| 112 | +# Bug 3: go func(){defer} 误报 |
| 113 | +if (match(line, /defer/) && loop_depth > 0) # 无 flit_depth 判断 |
| 114 | +``` |
| 115 | +
|
| 116 | +**After**(全部修复): |
| 117 | +```awk |
| 118 | +# Fix 1: POSIX 字符类 |
| 119 | +if (match(line, /^[[:space:]]*for([[:space:]]|$)/)) |
| 120 | + |
| 121 | +# Fix 2: gsub 字符级计数 |
| 122 | +tmp = line; opens = gsub(/\{/, "", tmp) |
| 123 | +tmp = line; closes = gsub(/\}/, "", tmp) |
| 124 | +total_depth += opens - closes |
| 125 | + |
| 126 | +# Fix 3: func literal 作用域隔离 |
| 127 | +if (match(line, /defer/) && loop_depth > 0 && flit_depth == 0) |
| 128 | +``` |
| 129 | +
|
| 130 | +## Notes |
| 131 | +
|
| 132 | +- BSD awk 的 `\b` 词边界语法是 `[[:<:]]`(词首)和 `[[:>:]]`(词尾),与 gawk 的 `\b` 不同 |
| 133 | +- `IGNORECASE = 1` 是 gawk 扩展,POSIX awk 不支持 — 用 `tolower()` 替代 |
| 134 | +- macOS 上 `awk` 就是 BSD awk;即使安装了 `gawk`,脚本中写 `awk` 仍调用 BSD 版本 |
| 135 | +- 自动检测已集成到 `scripts/setup/check.sh`("Guard Script Portability" 段) |
| 136 | +
|
| 137 | +## References |
| 138 | +
|
| 139 | +- 来源项目:vibeguard commit `6c5b652`(2026-04-02) |
| 140 | +- 涉及文件:`guards/go/check_defer_in_loop.sh`、`guards/rust/check_semantic_effect.sh` |
| 141 | +- POSIX 正则标准:IEEE Std 1003.1 (ERE) |
0 commit comments