示例说明
裸 bash 脚本的默认行为是错完继续跑:未定义变量展开成空串、管道里第一个命令失败被末尾成功掩盖,最终以零退出码伪装成功毁掉数据。三个开关分别堵住三条路:-e 失败即退、-u 未定义即炸、-o pipefail 管道任一段失败整体失败。ERR 陷阱把失败现场——行号、命令、退出码——在退出前打印出来。
技术要点
- set -euo pipefail 是脚本的默认开头
- ERR 陷阱输出行号与命令现场
- || true 显式声明容忍的点
代码解读
on_error 从 $? 取退出码、BASH_LINENO 取行号、BASH_COMMAND 取失败命令,三条信息定位任何失败点;readonly 的 SCRIPT_DIR 是取脚本所在目录的惯用法(含空格路径安全)。count_lines 里的 || true 演示显式容忍:grep 无匹配返回一属正常,用 or true 明示这不是疏漏。
使用建议
函数内 set -e 有边界情况,关键路径显式判断返回值更稳。source 该脚本时 ERR 陷阱会影响宿主,加作用域判断。shellcheck 静态检查应纳入 CI。
代码示例
#!/usr/bin/env bash
# 严格模式三件套 + 错误陷阱
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
on_error() {
local exit_code=$?
local line=${BASH_LINENO[0]:-unknown}
echo "Error: 脚本在第 ${line} 行失败,退出码 ${exit_code}" >&2
echo "Error: 执行到此处时的命令:${BASH_COMMAND}" >&2
exit "${exit_code}"
}
trap on_error ERR
# pipefail 演示:管道中段失败不再被吞掉
count_lines() {
grep -c '^data:' "$1" || true # 显式容忍:grep 无匹配返回 1
}
main() {
local input="${SCRIPT_DIR}/events.log"
local lines
lines=$(cat "$input" | wc -l) || return 1
echo "total lines: ${lines}"
echo "data lines: $(count_lines "$input")"
}
main "$@"