ShellCheck 能检测哪些 shell 脚本问题?
更新时间
ShellCheck 是一个开源的 shell 脚本静态分析工具,它能在不运行脚本的情况下自动找出 bug。它主要检测三类问题:语法与引用错误、可移植性问题,以及较高层的语义问题。适用条件是:你有一段 sh/bash 脚本,希望在上线或执行前发现潜在错误;你可以把脚本粘贴到 shellcheck.net 在线试用,也可以用 cabal、apt、dnf、pkg 或 brew 在本地安装。
语法与引用问题
这是 ShellCheck 最基础也最常见的一类检测,典型场景是引号使用不当。
官网示例中有一段脚本,用单引号包裹了包含变量名的提示信息:
echo 'Backup not enabled in $HOME/.myconfig, exiting'
单引号会让 $HOME 不被展开,用户看到的是一条字面文本而不是实际路径。ShellCheck 会指出这类引用问题。同一示例里还有 find 命令中未加引号的 *.tar.gz 通配模式,以及变量 $verbose 直接拼进命令行的写法,都属于引用与展开相关的隐患。
这类问题的共同点是:脚本能跑,但结果和作者意图不一致,或者在某些文件名、参数下突然出错。
可移植性问题
ShellCheck 会检查 shebang 声明与脚本实际写法是否匹配。官网给出的例子是脚本开头写 #!/bin/sh,但正文使用了 bash 专有语法:
#!/bin/sh
for n in {1..$RANDOM}
do
...
if (( n % 3 == 0 ))
...
done
{1..$RANDOM} 的花括号展开和 (( )) 算术语法都不是 POSIX sh 的标准能力。ShellCheck 会提示这种可移植性冲突,并建议把 shebang 改为 #!/bin/bash 以允许 bashism。如果你的脚本要在多种 shell 或精简环境(如某些容器基础镜像)中运行,这类提示尤其值得处理。
较高层的语义问题
除了逐行的语法检查,ShellCheck 还能发现一些跨语句的逻辑问题。官网示例包括:
- getopts 分支不完整:
while getopts "nf:" param的case中只处理了f)和v),n选项没有对应分支,脚本行为可能与预期不符。 - 文件类型处理分支:
case "$file"中同时出现*.gz)和*.tar.gz),前者会先匹配到.tar.gz文件,导致后者永远不生效。 - 命令替换写法错误:
if [[ "$$(uname)" == "Linux" ]]中$$是当前进程 PID,作者本意应是$(uname),这类笔误很难靠肉眼发现。
这些问题的价值在于:它们不是语法错误,脚本照样能执行,但逻辑已经偏离意图。
常见错误类型速查
| 问题类型 | 示例中的表现 | 后果 |
|---|---|---|
| 引号使用不当 | 单引号内写 $HOME |
变量不展开,提示信息错误 |
| 未引用变量/通配符 | $verbose、*.tar.gz 直接拼接 |
参数含空格或特殊字符时命令出错 |
| shebang 与语法冲突 | #!/bin/sh 配 (( )) |
换 shell 环境后脚本失败 |
| 选项分支缺失 | getopts 中漏掉 n) |
选项被静默忽略 |
| 分支顺序错误 | *.gz) 排在 *.tar.gz) 前 |
后续分支不可达 |
| 命令替换笔误 | $$(uname) |
判断条件永远不成立 |
怎么用起来
在线方式:打开 shellcheck.net,把脚本粘贴进编辑器,输出窗口会显示检测结果,页面还提供 “Apply fixes” 应用修复和 “Report bug” 反馈问题。本地方式:按官网说明用 apt、dnf、pkg、brew 或 cabal 安装后,在命令行对脚本文件运行检查。它也被集成到主流编辑器中作为 linter,并可在 CodeClimate、Codacy、CodeFactor 上自动检查 GitHub 仓库。
使用时的一个实用建议:先处理引用和可移植性两类提示,它们最容易引发实际故障;语义类提示则需要结合脚本意图判断,因为工具给出的是可疑点而非确定错误。