shell脚本必须主动检查$?判断命令成败,不可依赖无报错假象;推荐set -euo pipefail全局控制,辅以||/&&短路逻辑或guard函数封装错误处理。

Shell脚本里命令执行失败,不能靠“看起来没报错”来判断——必须主动检查退出状态码($?),并根据业务逻辑决定是终止、重试、跳过还是清理。原生Bash默认忽略失败,所以错误处理不是可选项,而是脚本上线前的必做项。
用 $? 显式捕获并响应退出码
每个命令结束后,$? 就存着它的退出状态:0 表示成功,非0 表示某种失败(注意:不是所有非0都代表“异常”,比如 grep 找不到匹配时返回 1,这是正常行为)。关键是要结合命令语义来解读。
- 执行命令后立刻读取
$?,不要隔行或被其他命令覆盖 - 避免只靠输出是否为空判断成败——
ls /empty_dir成功但无输出,ls /root失败却可能有错误文本 - 典型写法:
cp source.txt dest.txt<br>if [ $? -ne 0 ]; then echo "复制失败"; exit 1; fi
用 set -e 或 set -euo pipefail 快速失败
在脚本开头加 set -e,能让任何命令返回非0时立即退出,省去逐条检查。但它有盲区:管道中的中间命令、if 判断里的命令、&&/|| 后的分支不会触发退出。
- 更推荐
set -euo pipefail:
--u报未定义变量错误
--o pipefail让整个管道只要任一环节失败就整体失败(默认只看最后一条) - 慎用于需容错的场景,比如
if [ -f "$file" ]; then ...中的测试命令本身预期可能失败 - 临时关闭可用
set +e,用完再set -e恢复
用 || 和 && 实现轻量级分支控制
适合单行、明确意图的操作,比如“失败就记录日志”或“成功才继续”。它本质是基于上一条命令的退出状态做短路判断。
rm file.txt || echo "删除失败,文件可能不存在"-
mkdir logs && touch logs/app.log—— 只有 mkdir 成功,才会执行 touch - 注意:
cmd1 || cmd2 && cmd3等价于(cmd1 || cmd2) && cmd3,逻辑优先级易混淆,复杂逻辑建议用 if
封装 guard 函数统一错误策略
当多个命令需要一致的错误响应(如统一记录日志、发送告警、执行 cleanup),重复写 if 太冗余。可定义一个 guard 包装器,把“执行+校验+响应”收口。
- 示例:
guard() { "$@" || { echo "ERROR: $* failed with code $?"; cleanup; exit 1; }; }
调用:guard cp config.yaml /etc/myapp/ - 进阶可支持重试(如网络请求)、静默模式、不同错误等级回调
- 类似 sh-guard 这类库,就是把这类模式标准化、可配置化,适合中大型运维脚本











