| name | awk-posix-compat |
| description | Shell 脚本中 awk 的 POSIX 兼容性指南。
Use when: 编写或审查包含 awk 的 shell 脚本,
尤其是需要 macOS + Linux 跨平台运行的场景。
触发词: awk, BSD awk, POSIX regex, [[:space:]],
guard 脚本, 跨平台 shell
|
| author | Claude Code |
| version | 1.0.0 |
| date | "2026-04-03T00:00:00.000Z" |
awk POSIX 兼容性指南
Problem
macOS 自带 BSD awk 严格遵循 POSIX 标准,不支持 GNU awk (gawk) 的正则扩展。
在 Linux (gawk) 开发、macOS (BSD awk) 运行时静默匹配失败 — 无报错但结果为空。
这类 bug 极难调试:脚本不报错,只是检测不到任何匹配,看起来像"没有问题"。
When to Activate
- 编写包含
awk '...' 的 shell 脚本
- 脚本需要在 macOS + Linux 上运行
- 使用 awk 正则做代码模式检测(guard/linter/hook)
- 错误现象:Linux 上能检测到问题,macOS 上检测为 0
When to Activate
- A shell guard or hook uses
awk regexes and must run on both macOS and Linux.
- A pattern works under GNU awk but silently misses under BSD awk.
- A guard needs brace-depth, loop-scope, or nested-function analysis in plain POSIX awk.
Solution
1. 正则替换规则(6 条)
| GNU awk 扩展 | POSIX 替代 | 说明 |
|---|
\s | [[:space:]] | 空白字符 |
\S | [^[:space:]] | 非空白字符 |
\d | [0-9] | 数字 |
\w | [A-Za-z0-9_] | 单词字符 |
\b | [[:<:]] / [[:>:]] | 词边界(BSD 语法) |
\+ | {1,} 或重写模式 | 一次或多次 |
awk '/\s+defer\s/' "$file"
awk '/[[:space:]]+defer[[:space:]]/' "$file"
2. 字符级计数(gsub 法则)
禁止行级正则匹配计数大括号:
awk '/\{/ { depth++ }' "$file"
awk '{
tmp = $0; opens = gsub(/\{/, "", tmp)
tmp = $0; closes = gsub(/\}/, "", tmp)
depth += opens - closes
}' "$file"
原因:/{/ 是行级匹配 — 一行有 func() { if x { 两个 {,行级只计 1 次,导致 depth 跟踪错误,后续作用域判断全部偏移。
3. 作用域隔离(嵌套结构检测)
检测"X 在 Y 内"的模式(如 defer 在 for 循环内)时,需要区分语法层级:
if (is_func_literal && loop_depth > 0) {
flit_depth++
flit_base[flit_depth] = total_depth
}
Verification
bash scripts/setup/check.sh
find guards/ -name '*.sh' -print0 \
| xargs -0 grep -rnE '/[^/"]*\\[sdwb]' \
| grep -vE '^\s*#|grep |sed '
Example
场景:check_defer_in_loop.sh 在 macOS 上检测不到任何 defer-in-loop 问题
Before(3 个 bug):
# Bug 1: \s 在 BSD awk 静默失败
if (match(line, /^\s*for(\s|$)/))
# Bug 2: 行级计数漏计
/\{/ { total_depth++ }
# Bug 3: go func(){defer} 误报
if (match(line, /defer/) && loop_depth > 0) # 无 flit_depth 判断
After(全部修复):
# Fix 1: POSIX 字符类
if (match(line, /^[[:space:]]*for([[:space:]]|$)/))
# Fix 2: gsub 字符级计数
tmp = line; opens = gsub(/\{/, "", tmp)
tmp = line; closes = gsub(/\}/, "", tmp)
total_depth += opens - closes
# Fix 3: func literal 作用域隔离
if (match(line, /defer/) && loop_depth > 0 && flit_depth == 0)
Red Flags
- The awk program uses GNU-only escapes such as
\s, \d, \w, or \b.
- A script counts braces with line-level regex matches instead of character counts.
- A macOS check returns no findings while Linux finds real violations.
Checklist
Notes
- BSD awk 的
\b 词边界语法是 [[:<:]](词首)和 [[:>:]](词尾),与 gawk 的 \b 不同
IGNORECASE = 1 是 gawk 扩展,POSIX awk 不支持 — 用 tolower() 替代
- macOS 上
awk 就是 BSD awk;即使安装了 gawk,脚本中写 awk 仍调用 BSD 版本
- 自动检测已集成到
scripts/setup/check.sh("Guard Script Portability" 段)
Red Flags
- Using gawk-only regex in hook scripts -
\s, \w, and \b can pass on Linux and fail on macOS BSD awk.
- Counting braces per line only - nested blocks on a single line can corrupt scope tracking.
- Treating function literals as loop bodies -
go func(){ defer ... } inside a loop is not the same as defer in the loop scope.
- Testing only on one awk implementation - portability bugs usually appear only when the script runs under BSD awk.
Checklist
References
- 来源项目:vibeguard commit
6c5b652(2026-04-02)
- 涉及文件:
guards/go/check_defer_in_loop.sh、guards/rust/check_semantic_effect.sh
- POSIX 正则标准:IEEE Std 1003.1 (ERE)