Shell 运维脚本基础:严格模式与安全参数处理
用 set -Eeuo pipefail、参数校验、trap 清理与预览确认,写出半夜出事也不会闯祸的运维脚本。
运维脚本大多在凌晨跑、在故障时跑、在没人盯着的时候跑。一个脚本最可怕的结局不是报错,而是半路出错却继续执行:备份只做了一半、文件被挪了一半、服务重启了一半。这篇文章讲怎么写一个「要么全成,要么明确失败」的 Shell 脚本。
#严格模式:set -Eeuo pipefail 的每一段都要懂
#!/usr/bin/env bash
set -Eeuo pipefail四个开关各管一件事,缺一个都有漏洞:
-e:命令返回非零就退出。没有它,脚本会「干到底」,前面的失败全被后面的成功掩盖。-u:引用未定义变量就报错退出。没有它,$未定义变量是空字符串,rm -rf $DIR/可能变成rm -rf /。-o pipefail:管道中任何一个命令失败,整条管道就算失败。没有它,mysqldump xxx | gzip > backup.sql.gz里 mysqldump 失败时,gzip 可能照常成功,你得到的是一个空备份文件,还显示「成功」。-E:让trap ERR能捕获到子函数和子 shell 里的错误,配trap 'echo "line $LINENO failed"' ERR能定位失败行。
最常见的反模式是脚本里写着 set -e,但到处用 cmd || true 把错误吞掉。要么明确接受失败并处理,要么就让它炸出来——|| true 是给「失败也无所谓」的命令用的,不是给错误处理偷懒用的。
#参数校验:先拒后行
严格模式只解决「中途出错」,参数错了要在一开始就拦住:
#!/usr/bin/env bash
set -Eeuo pipefail
usage() {
echo "用法: $0 <目录>" >&2
exit 2
}
[[ $# -eq 1 ]] || usage
work_dir=$1
# 目录必须存在、必须是目录、必须可读
[[ -d "$work_dir" ]] || { echo "错误: 不是目录: $work_dir" >&2; exit 2; }
[[ -r "$work_dir" ]] || { echo "错误: 目录不可读: $work_dir" >&2; exit 2; }注意变量永远加双引号:"$work_dir"。不加引号,文件名带空格时会被拆成多个参数,rm -rf $work_dir/ 直接拆成两条命令——这是脚本误删的头号原因。
#临时文件和清理:trap EXIT
脚本创建临时文件,失败退出时会留下垃圾;更糟的是下次运行读到残留文件。用 trap ... EXIT 保证无论正常还是异常退出都清理:
tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT
# 之后脚本任何位置失败退出,tmp_dir 都会被清掉mktemp -d 生成的目录名带随机后缀,比手写 /tmp/myjob_$$ 安全(避免符号链接攻击和路径冲突)。绝对不要用固定的 /tmp/xxx 路径,多实例并发跑同一个脚本时会互相踩。
#危险操作:预览 + 确认开关
涉及删除、重启、发布的操作,脚本默认只打印动作,加 --apply(或 -y)才真执行。我在生产脚本里常用的写法:
apply=false
[[ "${1:-}" == "--apply" ]] && apply=true
do_backup() {
echo "[action] 备份 $src -> $dst"
if $apply; then
cp -a "$src" "$dst"
fi
}
do_backup输出里用 [action] 前缀标记将要发生的操作,人类看日志一眼就能核对。删除类操作再额外加一道确认:
if $apply; then
read -r -p "确认删除 $target 下的过期备份? [y/N] " ans
[[ "$ans" == "y" ]] || { echo "已取消"; exit 0; }
fi#日志:打清楚、不打敏感信息
脚本要能在事后复盘,日志至少要包含:时间、操作对象、结果。用 set -x 调试时尤其小心——它会把命令行原样打出来,export PASSWORD=xxx 这种就会泄露。
log() { printf '%s %s
' "$(date '+%F %T')" "$*"; }
log "开始同步 $work_dir"
# ...
log "同步完成,共 ${count} 个文件"我在一个事故里见过:脚本用 set -x 跑,把带数据库密码的连接串打进了 CI 日志,日志又同步到第三方平台。含密码的命令永远不要出现在 set -x 的输出里,敏感参数从环境变量读,且只在需要时导出。
#发布前检查清单
写完脚本,bash -n 只查语法,shellcheck 能查出一堆真问题:
bash -n ops.sh
shellcheck ops.shShellCheck 的警告里,SC2086(变量没加引号)、SC2164(cd 失败没处理)、SC2012(ls 解析)都是真实事故的高发项。我的经验是:任何超过 50 行的运维脚本都值得过一遍 shellcheck,它抓出来的问题远比「再小心一点」管用。
最后总结成一句话:set -Eeuo pipefail + 双引号 + trap 清理 + 预览确认,这四样配齐,脚本的「半夜出事率」能降一个量级。
写于 2026 年 8 月 21 日
- 栏目
- 技术文章
- 约
- 3.6 分钟
- 字数
- 2.2K
- 阅读
- 241
本文为原创记录,转载请注明出处。如果这篇替你省了时间,欢迎留言说说你踩到的坑。
同题 · related
留言 · remarks
00 条还没有留言,来说点什么吧。