set -e 并不总是生效,它仅对简单命令的非零退出码敏感,而对 if、&&/||、管道中间命令、子 shell 等场景静默忽略;需配合 pipefail、手动检查 $?、trap 清理等机制才能实现真正健壮的错误处理。
set -e 并不总是生效很多人以为加了 set -e 就能自动退出所有错误,其实它对很多常见场景完全静默:if 判断、&&/|| 链式操作、管道中的中间命令,甚至子 shell 里的失败都不会触发退出。
set -e 只对“简单命令”(simple command)的非零退出码敏感,而 command1 | command2 中的 command1 失败不会让整个 pipeline 返回非零,除非加 set -o pipefail
if false; t
hen echo ok; fi 不会因 false 退出——这是语法结构本身的设计,set -e 主动让渡控制权( some_command ) 启动子 shell 时,其内部失败也不会传播到父脚本,除非显式检查 $?
$? 或用 || 显式处理依赖 set -e 容易产生漏网之鱼。健壮脚本应把关键步骤的失败当作“需要决策的事件”,而不是靠全局开关甩手不管。
cp、curl、mysql),紧接其后用 || { echo "cp failed"; exit 1; }
if ! some_command; then ... fi 比 some_command || ... 更清晰,尤其当错误处理逻辑不止一行时$?:如果被调脚本返回 100 表示重试,返回 1 表示致命错误,你得区分对待,不能只看是否为 0set -o pipefail,否则 grep 找不到内容就等于“成功”默认情况下,cmd1 | cmd2 | cmd3 的退出状态只取 cmd3 的值。哪怕 cmd1 已经因为权限问题失败,只要 cmd3(比如 head -1)顺利执行完,整个管道就返回 0。
set -o pipefail
if ! output=$(find /root -name "*.log" 2>/dev/null | grep -E '\.log$' | head -1); then
echo "no log file found or permission denied"
exit 1
fi
set -o pipefail,上面 find /root 的 Permission denied 错误会被吞掉,$output 为空但脚本继续运行pipefail 让管道整体返回第一个失败命令的退出码,这才是符合直觉的行为pipefail,生产环境若需兼容 POSIX,只能拆成临时文件或分步检查trap,别指望 set -e 能兜底Ctrl+C(SIGINT)、超时(SIGTERM)或磁盘满导致的 SIGPIPE,set -e 完全无感。这些场景下资源(文件句柄、临时目录、锁文件)不释放,就会留下故障残留。
trap 'rm -f "$tmpfile"; rmdir "$tmpdir" 2>/dev/null' EXIT INT TERM 确保退出前清理EXIT trap 在所有退出路径(包括 exit 0、exit 1、自然结束)都会触发;INT/TERM 则捕获对应信号trap 里调用复杂函数或依赖未初始化的变量——trap 执行时上下文可能已部分销毁健壮性不是靠一个开关撑起来的,而是每处 IO、每次子进程、每个信号点都得想好“出事了怎么办”。最危险的,是以为加了 set -e 就万事大吉,结果上线后某个 grep 在空输入上静默失败,后续逻辑全跑偏。