编程与开发 · 第 13 期

Shell 脚本不健壮,set -euo pipefail 救命

90% 的脚本都缺这三行

90% 的 Shell 脚本都不健壮:变量没定义不报错、命令失败继续跑、管道里前一个崩了后一个还在用。set -euo pipefail 三件套能救命:-e 命令失败就退出,-u 未定义变量报错,-o pipefail 管道任一失败算失败。缺这三行,你的脚本就是一颗定时炸弹。
⏱ 约 9 分钟 🎯 写过 Shell 脚本但没加 set 的人 📦 源:linux-command §9

01一个数字:90% 的脚本都缺这三行

随便打开一个团队的 Shell 脚本,第一行通常是 #!/bin/bash,然后直接开干。问题是 Bash 默认行为是宽容到危险:

缺 -e:命令失败不退出,脚本继续跑,用错误结果干坏事
缺 -u:变量名打错不报错,当成空字符串,删错文件
缺 pipefail:管道 a | b 里 a 崩了,b 用空输入跑,结果全错

最经典的惨案:rm -rf $FOO/bar,本意删某个目录下的 bar,但 $FOO 拼错了或没定义,Bash 把它当空字符串,命令变成 rm -rf /bar——从根目录开始删。加了 set -u,$FOO 未定义直接报错退出,惨案不会发生。set -u 是 rm 的最后一道防线。

缺 set -u 的脚本,
是rm -rf 的引信。
灏天文库 · 编程与开发 P.39

02脚本体检:给脚本打健壮性分

下面是几段真实风格的脚本,点"体检"看它缺哪些防护、得多少分。三个维度:-e(失败退出)、-u(变量检查)、pipefail(管道安全)。

🩺 Shell 脚本健壮性体检
点示例切换脚本,点体检看三项检查和总分。

03三个开关各自的救命场景

set -e:命令返回非零就退出。但有个坑——if/while/&&/|| 里的命令失败不触发 -e(因为它们本来就是要"测试"失败)。所以 if grep ...; then 里 grep 没找到不会让脚本退出,这是设计如此,不是 bug。

set -u:引用未定义变量就报错退出。但 ${VAR:-default} 和 ${VAR:+x} 这种带默认值的写法不触发 -u,所以你想要"未定义就用默认值"的变量,用 ${VAR:-} 包一下即可。

set -o pipefail:管道的退出码取所有命令里最大的非零,而不是默认的"只看最后一个"。没有它,false | true 退出码是 0(true 的),脚本以为成功了;有了它,退出码是 1(false 的),-e 能捕获到。pipefail 是 -e 在管道里生效的前提。

pipefail 是 -e 在
管道里生效的前提。
灏天文库 · 编程与开发 P.40

04还有这些坑:cd 失败、子 shell、trap 清理

加了三件套还不够,还有几个常见坑:

一个真正健壮的脚本开头长这样:#!/usr/bin/env bash; set -euo pipefail; IFS=$'\n\t'。最后一行把字段分隔符设成只认换行和 tab,避免空格把文件名切碎——和 find 的 -print0 同一个道理。健壮不是加一行 set,是一套防护组合。

05带走这套清单

✅ Shell 健壮性 6 条可执行规则

  1. 脚本开头必加:set -euo pipefail,缺一是定时炸弹。
  2. set -u 是 rm -rf 的最后防线:未定义变量直接报错退出。
  3. set -o pipefail 是 -e 在管道里生效的前提,缺它管道失败被吞。
  4. cd 后接 || exit 1,避免 cd 失败在错目录跑 rm。
  5. 用 trap '...' EXIT 清理临时文件,比手动删可靠。
  6. 健壮脚本模板:set -euo pipefail + IFS=$'\n\t' + trap 清理。
加 set -euo pipefail,
脚本从炸弹变护栏。
灏天文库 · 编程与开发 P.41