Skip to content

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 $name

shellcheck 会提示:

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 failures

run 执行函数,$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,能一条命令别拆十行。脚本的价值在于把麻烦的事变简单,不是把简单的事写复杂。