Shell 脚本的健壮性:set -euo pipefail 治不了的病
Shell 脚本的健壮性:set -euo pipefail 治不了的病
默认的 bash 是个乐天派
写脚本时最容易犯的错,是以为出错就会停。实际上 bash 的默认行为是:一条命令失败了,它接着往下跑。
于是就有了这种事故——备份脚本 cd /some/dir 失败(目录不存在),后面一行 rm -rf ./* 就在错误的目录里执行了。没有任何报错,脚本退出码还是 0。
所以稍微正经一点的脚本,开头都会写这行:
set -euo pipefail
这三个开关各自管一件事,很多人抄了但没弄清分别管什么。
三个开关分别治什么
| 开关 | 作用 | 治的病 |
|---|---|---|
-e |
任何命令返回非 0 就立即退出 | 出错继续往下跑 |
-u |
引用未定义变量就报错退出 | $DIR/* 里 $DIR 忘了定义,变成 /* |
pipefail |
管道里任一环节失败,整条管道算失败 | a | b 只看 b 的退出码 |
-u 这条被低估得最厉害。rm -rf "$BUILD_DIR"/* 里如果 $BUILD_DIR 因为拼写错误而为空,在没开 -u 的情况下就变成了 rm -rf /*。这不是段子,是真事。
它治不了的几种病
一、条件语句里的失败不会退出。
set -e
if grep -q pattern file; then echo "found"; fi
echo "继续执行"
grep 没匹配到会返回 1,但因为它在 if 的条件位置上,-e 不会触发。这是有意设计的,不是 bug——但写的时候要知道。
二、子 shell 和命令替换。
set -e
result=$(false; echo "still here")
echo "$result"
$(...) 里跑的是子 shell,整体退出码取最后一条命令。默认不当成失败,-e 也就没反应。要抓这种情况,除了 pipefail,还得在函数里自己检查返回值。
三、trap 才是真正的收尾手段。
set -e 只负责"停",不负责"清理"。脚本中途退出时,临时文件、锁文件、半成品都得自己收。
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
TMP="$(mktemp)"
trap 'rm -f "$TMP"' EXIT
trap 'echo "[失败] 第 $LINENO 行: $BASH_COMMAND" >&2' ERR
这里的 set -E 常被忽略:它让 ERR trap 能穿透函数和子 shell。少了它,函数内部的失败不会触发 trap,出错时你只能看到一个光秃秃的退出码。
四、kill -9 谁也管不了。
trap ... EXIT 在进程被 SIGKILL 时不会执行。所以别把"数据一致性"全押在 trap 上;关键状态要能自愈,比如启动时检查遗留的锁文件、用临时文件加 mv 做原子替换。
顺手一个 safeguards:${VAR:?}
set -u 只能挡住"变量没定义",挡不住"定义了但是空字符串"。而危险操作里更常见的恰恰是后者——配置项读到了,但内容是空的。
rm -rf "${BUILD_DIR:?}/"*
:? 的意思是"为空或未定义就立刻报错退出,并打印后面那句话"。上面的写法在 BUILD_DIR 为空时直接中止,不会退化成 rm -rf /。凡是拿变量拼路径再交给 rm、mv 的地方,都值得加这个后缀。
出错信息比退出码更有用
set -e 只给你一个退出码,出问题时你还是得回去翻脚本。加一行 ERR trap,把行号和正在执行的命令打出来:
trap 'echo "[失败] 第 $LINENO 行: $BASH_COMMAND" >&2' ERR
配合 set -x(只在调试时开)和 bash -x script.sh,定位成本能从"猜半小时"降到"看一眼"。
一个能直接用的开头
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
IFS 改成换行和制表符,是为了让 for f in $list 这种写法不会因为文件名里有空格而拆错。这是最后一条容易被漏掉、但代价很大的设置。
上线前自检
- 用
bash -n script.sh检查语法,用shellcheck找隐患; - 拿空目录、不存在的路径、无权限的文件各跑一次,看它是"明确报错退出"还是"静默继续";
- 在
trap EXIT里打印实际执行到了哪一步,出事时能定位; - 危险操作(
rm -rf、mv覆盖)前打印目标路径并read -r确认,或者至少在日志里留痕。
脚本写得好不好,不体现在顺风时跑得多快,而体现在出错时它停不停、停得干不干净。
- 点赞
- 收藏
- 关注作者
评论(0)