目标
最吓人的 shell bug 是参数 bug:缺失值悄悄吞掉下一个旗标、空字符串通过真实性检查、叫 -weird 的目标被当成旗标吃掉。optstring 前导冒号加显式校验加 – 转义,二十行内把这些洞全部堵上,再用一张语法表把每种误用钉在 CI 里。
步骤要点
- 写 getopts 循环:optstring 用 “:e:r:vn” 前导冒号接管错误处理,: 分支报缺值、? 分支报未知旗标,usage 函数统一输出帮助。
- 循环结束后 shift $((OPTIND-1)) 拿位置参数,并对必填项做非空校验、对枚举值做 case 白名单校验。
- 支持 – 转义:位置参数以横线开头时(deploy.sh -e prod – -weird-name)原样接收而非被解析。
- 显式区分空值与缺值:-e “” 在解析期就报“value is empty”,而不是让空环境名流到下游。
- 用表驱动测试跑六行用例(缺值、未知旗标、空值、空串、合法、带 –),每行断言退出码并输出 ok,进 CI 防回归。
排错提示
需要 –env 长选项就在 getopts 前加一趟预处理把长翻译成短,或干脆文档化只用短旗标,POSIX 没有 lenovo 长选项内建;-e 后缺值吞掉 -v 是经典 bug,前导冒号让缺值走 : 分支而不是把下一个 token 当值吃掉;getopts 遇到第一个非选项就停,要么文档化“旗标在前”,要么 shift 位置参数后再跑第二趟 getopts 支持交错风格。
适用边界
getopts 只处理单字符旗标,更丰富的语法该交给有真正参数解析器的语言,而不是 shell 状态机模拟;语法表验证的是文法不是语义,旗标行为要有独立的测试。
代码示例
# the optstring colon is the whole trick:
# ":e:r:vn" -> silent errors you handle yourself
# "e:r:vn" -> getopts prints its own message and you lose control
while getopts ":e:r:vn" opt; do
case "$opt" in
e) ENV=$OPTARG ;;
:) echo "error: -$OPTARG requires a value" >&2; usage ;;
\?) echo "error: unknown flag -$OPTARG" >&2; usage ;;
esac
done
shift $((OPTIND - 1))
# grammar table proving it
# ok [-e] -> 2 (requires a value)
# ok [-z] -> 2 (unknown flag)
# ok [-e ""] -> 2 (empty value)
# ok [-e prod] -> 0