set -e 默认不捕获管道中非末尾命令的失败,需搭配 set -uo pipefail;在 if/while/&&/||/子 shell 中及显式忽略时也不终止,正确用法是全局启用并理解其控制流语义。

set -e 的基本启用方式
在脚本开头加 set -e 就能让它对非零退出码敏感,命令失败时立刻终止。这不是可选优化,而是防止“错误被吞掉”的底线操作。
推荐写法是:#!/bin/bash 后紧跟 set -euo pipefail——其中 -u 防止未定义变量静默展开,-o pipefail 修正管道默认只看最后一个命令退出码的缺陷。
- 单独用
set -e不够,比如grep "xxx" file | head -1中grep失败会被忽略 -
set -euo pipefail是生产脚本的事实标准开头,不是“更严谨”,而是“不这么写就容易出事” - 别写成
set -e -u -o pipefail:语法错误,-o必须紧接选项名
哪些失败不会触发 set -e 终止
它不是无脑拦截所有非零码,而是尊重 Shell 的控制流语义。以下情况即使命令返回 1,脚本也继续执行:
- 出现在
if、while、until条件判断中的命令,例如if ! command; then ... fi - 用
&&或||连接的命令链,如rm file || echo "file not exists" - 子 shell 内执行:
(command_that_fails)不影响父脚本 - 显式吞掉退出码:
command || true或command || :
这种设计是优点:你不用为每个容错逻辑加 if,自然写法就能绕过终止。
管道和 grep 是高频翻车点
set -e 默认对管道无效,这是最常被忽略的坑。比如:
ps -ef | grep "myapp" | awk '{print $2}'
如果 ps -ef 成功但 grep 没匹配到任何行(退出码 1),整个管道仍返回 0,set -e 完全不响应。
- 必须搭配
set -o pipefail,才能让管道中任一环节失败都传导为整体失败 - 对
grep这类“查不到即失败”的命令,更稳妥的是用分组:if output=$(grep "xxx" file); then ... fi,把失败控制在条件分支里 - 避免裸用
PID=$(ps -ef | grep xxx | grep -v grep | awk '{print $2}'),没进程时直接退出
临时关闭 set -e 的安全写法
真需要容忍某条命令失败(比如检查文件是否存在),不要用 command || true 这种模糊写法——它掩盖了真实意图,也掩盖了其他潜在错误。
- 用
set +e关闭,执行完立刻set -e恢复,且必须配对出现 - 更推荐用
if ! command; then echo "expected failure"; fi,语义清晰,不破坏全局状态 - 绝对不要在函数里
set -e又不恢复:函数退出后set -e依然生效,会污染调用方逻辑
真正难的不是加 set -e,而是理解它在哪“不生效”——那才是错误真正藏身的地方。











