编程 ShellCheck 到底比 bash -n 多查了什么:三条高频规则与 CI 落地

2026-10-16 21:05:59

ShellCheck 到底比 bash -n 多查了什么:三条高频规则与 CI 落地

和 bash -n 的区别

ShellCheck 更接近编译器的前端:先完整解析脚本生成抽象语法树,再跟踪变量的定义与使用,理解管道、命令替换、子 shell、条件分支之间的控制流,最后套用内置规则库。规则库里每一条基本都对应一个真实发生过的事故。

bash -n 只做语法检查,不跟踪变量数据流。脚本能通过 bash -n,不代表变量没拼错、引号没漏。

三条高频规则

SC2086:变量不加引号会触发分词

$backup_dir 含空格时会被拆成多个参数,命令变形;变量为空时更糟,参数直接消失。

rm -rf $backup_dir     # 含空格 → 拆成多个参数
rm -rf "$backup_dir"   # 加引号

SC2164:cd 的退出码没被处理

发布脚本里的经典坑:先 cd 到发布目录再构建,cd 失败时脚本继续往下跑,后面所有相对路径都落到错误目录。

cd "$dir" || exit 1

SC2155:声明与赋值写在同一行

local var="$(command)" 会把命令替换的退出状态吞掉——local 自身返回 0,把失败退出码盖过去。声明和赋值要拆开:

local var
var="$(command)"

其它高频规则

  • SC2154:引用未定义或拼错的变量
  • SC2034:声明了但未使用的变量
  • SC2046:命令替换结果的分词错误
  • SC2206:用字符串存列表再展开,本身脆弱

集成方式与落地坑

VSCode 的 shellcheck 插件、pre-commit、CI 都可以直接调用 shellcheck 命令。

坑一:一次性对全仓库开启。 若仓库已积压大量历史脚本,先让「新改动和本次修改的文件」必须通过,存量脚本单独建待处理清单。常见失败路径是 CI 一次开最严规则,几十个脚本全红,流水线连红两周,最后把检查从 CI 摘掉。

坑二:不加判断地 disable。 某些规则确实会因脚本的动态特性误报,但加 # shellcheck disable= 前先弄清触发原因。真实反例:有人把变量名拼错,警告出现后没看文档,顺手加了 SC2154 的 disable,错误一路带到生产。

坑三:只检查主脚本、忽略被 source 的公共文件。 ShellCheck 对单文件分析准,但追不到跨文件变量传接。把整个 lib 目录纳入检查,必要时用 --external-sources 显式跟踪外部 source 文件。

坑四:对警告级别一刀切。 不同规则的可信度和误报率差别很大。先从 error 级别入手,warning 级按团队接受度分批开放,并记录哪些规则易误报。

落地建议

  • 先在新增脚本和本次改动涉及的脚本上启用,避免历史包袱拖垮节奏。
  • 把 shellcheck 放进 CI 的 lint 阶段,设「新增脚本必须零 error 警告」的硬门槛。
  • 所有 disable 注释必须写明理由,review 时顺着注释判断例外是否值得保留。
  • 用 -s 参数明确脚本类型,区分 bash 与 POSIX sh,减少平台差异带来的误报。
  • 把 ShellCheck 和 set -euo pipefail 结合使用:前者解决静态问题,后者提供运行时保护,二者互补。
复制全文 生成海报 Shell ShellCheck bash 运维

推荐文章

程序员茄子在线接单