5.5.2 错误处理策略与退出码
在Shell脚本中,错误处理的核心目标是“可预期、可定位、可恢复”。应根据业务影响与脚本职责定义统一的错误处理策略,明确哪些错误需立即终止、哪些可降级继续、哪些需重试或回滚。建议在脚本开头声明策略并贯穿全流程,避免“忽略错误”导致隐性故障扩散。
原理草图:错误处理与退出码流转
常见错误处理策略
- 立即终止(Fail Fast):关键路径失败即退出,防止脏数据或不一致状态扩散。
- 降级继续:非关键步骤失败仅记录警告,确保主流程可用。
- 重试机制:对瞬时错误(网络抖动、锁竞争)设置重试次数与退避时间。
- 回滚/清理:在失败时撤销已执行的部分操作,保证幂等与可重复执行。
- 错误汇总:批处理场景下收集错误信息,最后统一判断与输出。
退出码规范(建议)
- 0:成功
- 1:通用错误(未分类)
- 2:用法错误或参数缺失
- 3:环境依赖缺失(命令、目录、权限)
- 4:外部服务不可用(网络、端口、API)
- 5:数据校验失败(格式、完整性)
- 6:执行超时或重试失败
- 7:资源不足(磁盘、内存、句柄)
- 8:配置错误或加载失败
- 9:权限不足或鉴权失败
- 10:未知异常(保留兜底)
关键实践
- 使用 set -euo pipefail 提高错误显性化,但需对可控失败明确处理。
- 对关键命令采用显式检查:cmd || { log_error; exit 4; }。
- 在函数内返回非0退出码,由调用方统一处理,避免深层直接 exit。
- 统一错误输出到 STDERR,并输出必要上下文(时间、主机、步骤、输入参数)。
- 对外部依赖进行前置健康检查,减少运行时失败。
- 保证脚本幂等:多次执行结果一致,便于失败后重试。
示例:带重试与退出码的备份脚本#
脚本文件:/opt/ops/backup_mysql.sh
#!/usr/bin/env bash
set -euo pipefail
LOG="/var/log/backup_mysql.log"
DB_HOST="127.0.0.1"
DB_USER="backup"
DB_NAME="app"
OUT_DIR="/data/backup"
RETRY=3
log() { echo "$(date +'%F %T') [$1] $2" | tee -a "$LOG" >&2; }
check_env() {
command -v mysqldump >/dev/null || { log ERROR "mysqldump not found"; return 3; }
[ -w "$OUT_DIR" ] || { log ERROR "OUT_DIR not writable"; return 3; }
return 0
}
retry_cmd() {
local n=1
until "$@"; do
if (( n >= RETRY )); then
return 6
fi
log WARN "retry $n failed, sleep 2s..."
sleep 2
((n++))
done
}
backup() {
local ts file
ts="$(date +%F_%H%M%S)"
file="$OUT_DIR/${DB_NAME}_${ts}.sql"
retry_cmd mysqldump -h "$DB_HOST" -u "$DB_USER" "$DB_NAME" > "$file"
}
main() {
check_env || exit $?
if backup; then
log INFO "backup success"
exit 0
else
rc=$?
log ERROR "backup failed rc=$rc"
exit "$rc"
fi
}
main "$@"
命令执行与预期结果
chmod +x /opt/ops/backup_mysql.sh
/opt/ops/backup_mysql.sh
echo $?
# 预期:成功时退出码为0,日志记录INFO
命令解释
- set -euo pipefail:启用严格模式,未定义变量、管道失败立即报错。
- retry_cmd:封装重试逻辑,超过重试次数返回 6。
- check_env:检查依赖与目录权限,返回 3 表示环境依赖缺失。
示例:批处理降级继续并汇总错误#
脚本文件:/opt/ops/cleanup_logs.sh
#!/usr/bin/env bash
set -euo pipefail
LOG="/var/log/cleanup_logs.log"
HOSTS=(10.0.0.1 10.0.0.2 10.0.0.3)
FAILED=()
log() { echo "$(date +'%F %T') [$1] $2" | tee -a "$LOG" >&2; }
for h in "${HOSTS[@]}"; do
if ssh "$h" "find /var/log -name '*.log' -mtime +7 -delete"; then
log INFO "cleanup ok: $h"
else
log WARN "cleanup failed: $h"
FAILED+=("$h")
fi
done
if ((${#FAILED[@]} > 0)); then
log ERROR "failed hosts: ${FAILED[*]}"
exit 1
else
exit 0
fi
命令解释
- FAILED 收集失败主机列表,脚本末尾统一判断。
- 单机失败不终止,降级继续执行其他主机。
排错指南(常见错误与处理)#
- 脚本直接退出且无日志
- 排查:是否启用了set -e且未捕获错误。
- 处理:在关键命令处显式捕获并记录日志。 - 返回码始终为0
- 排查:函数内未return非0,或|| true掩盖错误。
- 处理:规范函数返回值,避免无意义的|| true。 - 重试无效
- 排查:重试函数未返回非0或管道错误被吞掉。
- 处理:开启pipefail,确保失败能冒泡。
练习#
- 将备份脚本加入“回滚/清理”,失败时删除生成的空文件。
- 为批处理脚本增加超时控制:单机执行超时返回退出码
6。 - 设计一套退出码规范并与日志级别映射,打印在脚本开头。