Appearance
18|脚本测试与工程化
前面写的脚本,能跑、能防错、能幂等。但脚本写完怎么确认没问题?改一处会不会影响别处?团队几个人一起写脚本怎么不冲突?这些是脚本工程化要解决的。
本篇讲两件事:用 shellcheck 静态检查脚本、给脚本写测试,以及脚本工程化的一些规范。
一、shellcheck:静态检查
Shell 语法松散,很多错误运行时才暴露——变量没引号、命令拼错、set -e 没加。shellcheck 是个静态检查工具,不运行脚本就能找出这些问题。
装一下:
bash
# RHEL 系(EPEL 仓库)
yum install -y epel-release shellcheck
# Debian 系
apt install -y shellcheck对一个脚本检查:
bash
shellcheck myscript.sh看一个 shellcheck 能抓到的典型问题。脚本里有这么一段:
bash
name="hello world"
echo $nameshellcheck 会提示:
text
In script.sh line 2:
echo $name
^---^ SC2086: Double quote to prevent globbing and word splitting.意思是 $name 没加引号,值含空格时会被拆成两个词,建议 echo "$name"。这正是第 2 篇讲的坑。
shellcheck 能抓的常见问题:
| 规则 | 问题 |
|---|---|
| SC2086 | 变量没加引号 |
| SC2034 | 变量定义了没用到 |
| SC2046 | 命令替换没加引号 |
| SC2155 | 声明并赋值掩盖了退出码(第 9 篇讲过) |
| SC2181 | 直接检查 $? 而不是用 if 命令 |
每个提示都带规则号,能查具体含义和修复建议。
二、集成到编辑器和 CI
shellcheck 可以集成进编辑器,写的时候实时提示。VS Code 装 shellcheck 插件,保存时自动检查。
进 CI 流程,每次提交自动检查。.gitlab-ci.yml 或 GitHub Actions 里加一步:
yaml
lint:
script:
- shellcheck scripts/*.sh这样脚本有问题 CI 会失败,挡在合并之前。
三、给脚本写测试
Shell 没有像 Python pytest 那样的测试框架,但用 bats(Bash Automated Testing System)能给脚本写测试。
装 bats:
bash
git clone https://github.com/bats-core/bats-core.git
cd bats-core
./install.sh /usr/local写个要测的函数 lib.sh:
bash
# lib.sh
get_port() {
case "$1" in
nginx) echo 80 ;;
mysql) echo 3306 ;;
*) echo "" ;;
esac
}写测试 test.bats:
bash
#!/usr/bin/env bats
setup() {
source ./lib.sh
}
@test "nginx 返回 80" {
run get_port nginx
[ "$status" -eq 0 ]
[ "$output" = "80" ]
}
@test "未知服务返回空" {
run get_port unknown
[ "$status" -eq 0 ]
[ "$output" = "" ]
}跑测试:
bash
bats test.bats
# nginx 返回 80
# ✓ nginx 返回 80
# ✓ 未知服务返回空
#
# 2 tests, 0 failuresrun 执行函数,$status 是退出码,$output 是输出。bats 让 Shell 脚本也能像正经程序一样写单元测试。
四、测试什么
不是所有脚本都要写测试。值得写测试的:
- 核心函数:解析逻辑、计算逻辑、状态判断。比如"从日志行提取 IP""判断磁盘是否超阈值"
- 常变的部分:改了容易出 bug 的逻辑,有测试能防回归
- 库函数:被多个脚本复用的公共函数
不值得写测试的:
- 一次性的、临时跑的脚本
- 纯粹调系统命令的胶水脚本(测系统命令没意义)
- 逻辑极简、一眼看穿的脚本
五、工程化规范
脚本多了、团队多人写,要有些规范:
目录结构:
text
scripts/
├── bin/ # 可执行脚本,加进 PATH
├── lib/ # 公共函数库(source 引用)
├── tests/ # bats 测试
└── README.md # 脚本说明公共函数放 lib/,被 bin/ 下的脚本 source 引用。测试放 tests/。
每个脚本有文档头:
bash
#!/usr/bin/env bash
# 用途: 检查磁盘使用率并告警
# 用法: disk-check.sh [阈值] [挂载点]
# 依赖: 无
# 作者: xxx脚本第一行 shebang 后写文档头,说明用途、用法、依赖。别人接手不用读代码就知道怎么用。
版本控制:脚本进 Git。.env、密钥、机器特定配置不提交。生产用配置管理工具或环境变量注入。
统一风格:缩进用 4 空格还是 tab 团队统一;变量名用小写下划线(disk_usage)还是驼峰统一。shfmt 工具能自动格式化 Shell 脚本,像 gofmt 一样消除风格争论。
六、shfmt:自动格式化
bash
# 安装
yum install -y shfmt # 或从 GitHub 下载
# 格式化
shfmt -i 4 -w myscript.sh # -i 4 缩进 4 空格,-w 写回文件shfmt 自动处理缩进、空格、续行,团队风格统一。配合 shellcheck,一个管格式一个管逻辑。
七、一个工程化脚本的检查清单
发布一个脚本前过一遍:
- [ ] shebang +
set -euo pipefail - [ ] shellcheck 通过、shfmt 格式化
- [ ] 参数校验、有用法提示
- [ ] 错误进 stderr、退出码规范
- [ ] 危险操作有检查和确认
- [ ] 临时文件有 trap 清理
- [ ] 文档头说明用途用法
- [ ] 核心逻辑有测试(值得测的话)
- [ ] 进 Git,敏感信息不硬编码
这个清单是脚本能上生产的最低标准。
系列收尾
到这里,Shell 脚本系列 18 篇走完了。从"脚本是什么、为什么要写"开始,学了语言基础(变量、条件、循环、数组、字符串、函数)、文本处理三件套(grep、sed、awk)、进阶用法(重定向、错误处理、幂等)、实战脚本,最后到测试和工程化。
Shell 脚本的核心价值是把重复的命令操作自动化。会写 Shell 脚本后,巡检、备份、批量操作、初始化这些日常重复的事,都能交给脚本自动跑,不用人盯着时间手动敲。复杂逻辑和数据处理的场景,换 Python 更合适——那是另一个系列。
写脚本记住一句话:能简单就别复杂,能用 ${} 别开 sed,能一条命令别拆十行。脚本的价值在于把麻烦的事变简单,不是把简单的事写复杂。