系统与运维

Bash 脚本防坑实战:set -euo pipefail 与 trap 让脚本真正可信

多数 Bash 脚本「跑完了」不代表「跑对了」:命令失败不退出、变量为空不报错、管道掩盖上游崩溃。本文用一套可直接套用的骨架(set -euo pipefail + trap + 引号纪律)根治这些静默失败。

作者:巧匠团队·7 分钟阅读·更新于 2026-08-31

裸脚本的三种「静默失败」

Bash 默认的容错行为是排障地狱的根源:命令失败不会退出(`-e` 未开)、未定义变量当空字符串用(`-u` 未开)、管道只看最后一个命令的退出码(`pipefail` 未开)。

后果是脚本「看起来跑完了」,实际中途就错了:`cd` 失败还在原目录继续删文件、变量名打错静默变空、`grep | head` 掩盖上游崩溃。线上事故往往就藏在这几行里。

cd /data/release && rm -rf old/    # cd 失败时 rm 根本不该执行
cp app.conf /etc/app.conf            # 失败了脚本照样往下走
echo "deploy done"                    # 假象:一切都好

第一道防线:set -euo pipefail

shebang 之后立刻加上 `set -euo pipefail`:`-e` 任一命令非零即退出;`-u` 引用未定义变量直接报错而非当空串;`-o pipefail` 让管道以第一个失败命令的退出码为准。

注意 `-e` 的例外:条件上下文(`if`/`while`/`&&`/`||`)中的失败不会触发退出——这是特性不是 bug,`cmd || exit 1`、`if grep -q ...` 这类写法依然要显式处理。

#!/usr/bin/env bash
set -euo pipefail
# 调试期可加:逐条回显执行的命令
# set -x

清理现场:trap 保底

脚本中途失败或被 Ctrl+C 杀掉时,临时文件、后台子进程、锁文件都会残留。用 `trap` 挂在 `EXIT` 上统一清理:无论正常结束、报错退出还是被中断,清理函数都会执行。

临时目录用 `mktemp -d` 创建(不要拼固定路径,避免并发冲突与符号链接攻击),清理函数只删自己创建的目录。

tmpdir=$(mktemp -d)
cleanup() { rm -rf "$tmpdir"; }
trap cleanup EXIT
# 需要 Ctrl+C / kill 也走一遍显式清理时可再加:
# trap cleanup INT TERM

引号纪律:所有展开都加双引号

未加双引号的变量会在空格处拆词、空变量直接消失:`rm -rf $dir` 在 dir 为空时等于裸 `rm -rf`(灾难级)。纪律很简单——所有 `$var` 一律写成 `"$var"`。

三个高频配套写法:文件名可能以 `-` 开头时用 `--` 分隔符或 `./` 前缀;必填参数用 `${1:?提示}` 缺失即报错退出;可选值用 `${2:-默认值}`。

rm -rf -- "$dir"                    # -- 防止文件名被当成选项
src="${1:?用法: $0 <源目录>}"           # 缺参数直接报错退出
out="${2:-./dist}"                    # 提供默认值

输入校验与幂等

外部输入(位置参数、环境变量、find 的结果)使用前先验证:依赖的命令用 `command -v` 检查,路径用 `[ -d ]`/`[ -f ]` 检查。宁可启动时报错,不要跑一半才崩。

让脚本可重复执行(幂等):`mkdir -p` 代替 `mkdir`,`cp -f`/`rsync` 代替裸 `cp`。半途失败的脚本可以直接重跑,不用人工清场。

command -v jq >/dev/null || { echo "需要 jq" >&2; exit 1; }
[ -d "$src" ] || { echo "目录不存在: $src" >&2; exit 1; }
mkdir -p "$out"                      # 幂等:已存在不报错

上生产前的调试与清单

三个工具覆盖九成问题:`bash -n` 只查语法,`bash -x` 逐行回显执行过程,shellcheck 静态分析能抓住上面几乎所有坑(包括未加引号的变量)。把 shellcheck 接进 CI,脚本质量问题基本绝迹。

最终清单:shebang + `set -euo pipefail` + trap 清理 + 全变量双引号 + 入参校验 + 幂等写法 + shellcheck 通过。照抄这套骨架,脚本就从「碰运气」变成「可信」。

bash -n deploy.sh        # 只查语法
bash -x deploy.sh        # 逐行回显执行(调试)
shellcheck deploy.sh     # 静态分析(推荐装进 CI)