掌握Bash脚本调试:开发者必备技巧
通过语法检查、xtrace、严格模式、陷阱、ShellCheck和精准日志调试Bash脚本。
掌握Bash脚本调试:开发者必备技巧
Bash脚本调试从一个问题开始:脚本在哪里做了与你预期不同的事情?良好的调试能让你看到语法错误、变量展开、命令顺序和退出状态,而不会让脚本变得杂乱无章。
本指南将介绍实用的Bash调试技巧,你可以在实际自动化脚本进入cron、CI或生产环境之前使用它们。
从语法检查开始
在追踪运行时行为之前,确保Bash能解析文件:
bash -n ./deploy.sh
bash -n 读取脚本并报告语法错误,但不运行命令。它能捕获缺失的 fi、done、then、引号和花括号。它不会捕获逻辑错误、缺失文件或运行时失败的命令。
例如,这个拼写错误会在任何代码运行前被捕获:
if [ -f "$CONFIG" ]; then
echo "Config found"
# 缺少 fi
在大规模编辑后以及添加更多调试输出之前,运行语法检查。
使用 set -x 追踪执行
最有用的内置调试器是xtrace:
set -x
some_command "$VALUE"
set +x
启用追踪后,Bash会在展开后、执行前打印每个命令。这有助于你看到变量是否为空、通配符是否展开、或命令是否收到了预期之外的参数。
对于整个脚本的追踪,运行:
bash -x ./deploy.sh
为了更清晰的追踪,设置 PS4 使每行包含源文件行号:
export PS4='+ ${BASH_SOURCE}:${LINENO}: '
bash -x ./deploy.sh
如果你的脚本处理机密信息,不要追踪打印令牌、密码或签名URL的部分。在这些命令之前关闭追踪:
set +x
login_with_secret "$API_TOKEN"
set -x
谨慎添加严格模式
这些选项能更早地捕获常见失败:
set -euo pipefail
set -e 在许多未处理的命令失败时退出。set -u 将未设置的变量视为错误。set -o pipefail 使管道中任何一个命令失败时整个管道失败,而不仅仅是最后一个命令。
它们很有用,但不能替代显式处理。像 grep 这样的命令可能因正常的“未找到”结果返回 1:
if grep -q "READY" status.txt; then
echo "ready"
else
echo "not ready"
fi
这比用 grep -q "READY" status.txt || true 隐藏结果更清晰。
打印正确的值
精准日志胜过散乱的 echo 行。打印影响你正在调试的分支的值:
printf 'DEBUG: user=%q env=%q target=%q\n' "$USER_NAME" "$ENVIRONMENT" "$TARGET_HOST" >&2
printf '%q' 显示shell转义后的值,使空格和特殊字符更容易发现。将调试输出发送到stderr,以便正常脚本输出在管道中仍然可用。
当命令失败时,立即捕获其状态:
run_migration
status=$?
if [ "$status" -ne 0 ]; then
echo "Migration failed with exit code $status" >&2
exit "$status"
fi
在保存 $? 之前不要运行其他命令,因为即使是 echo 也会替换它。
调试循环和条件语句
循环错误通常来自单词分割或意外输入。引用变量并安全地读取行:
while IFS= read -r line; do
printf 'line=%q\n' "$line" >&2
done < input.txt
对于条件语句,打印正在比较的确切值:
printf 'expected=%q actual=%q\n' "$EXPECTED" "$ACTUAL" >&2
if [[ "$ACTUAL" == "$EXPECTED" ]]; then
echo "match"
fi
如果在本地调试时需要暂停脚本,read 可以工作:
read -r -p "Press Enter to continue..."
在提交脚本前移除暂停,特别是如果它可能无人值守运行。
使用ShellCheck进行静态分析
ShellCheck能捕获许多Bash会愉快运行直到在边界情况中崩溃的问题:
shellcheck ./deploy.sh
它会标记未引用的变量、不可达代码、可疑测试、未使用变量和可移植性问题。将警告视为检查代码的提示,而不是自动证明脚本有误。有时你可能有意禁用某个警告,但应添加简短注释解释原因。
使用 trap 查看失败行
对于较长的脚本,错误陷阱可以告诉你失败发生的位置:
set -Eeo pipefail
trap 'echo "Error on line $LINENO: $BASH_COMMAND" >&2' ERR
set -E 帮助 ERR 陷阱在Bash的函数和子shell中传播。这在CI日志中很有用,因为你可能没有交互式shell。
要点
从 bash -n 开始,使用 bash -x 或针对性的 set -x 进行运行时追踪,并在行为不正确的分支周围添加精准的stderr日志。对于重要的脚本,运行ShellCheck并添加 ERR 陷阱,以便失败指向需要关注的命令和行。