Shell 脚本调试技巧:让 Bash 脚本不再神秘报错
Shell 脚本调试技巧让 Bash 脚本不再神秘报错写 Shell 脚本最烦的就是脚本跑到一半无声失败或者报错信息完全看不懂。掌握几个调试技巧能把脚本调试效率提升几倍。这篇文章总结生产环境实用的 Shell 调试方法调试模式、错误处理、日志记录、性能分析。基础调试模式set 选项最常用#!/bin/bash# 最佳实践在脚本开头加这三行set-euopipefail# 解释# -e → 任何命令失败返回非0立即退出脚本# -u → 使用未定义的变量时报错退出# -o pipefail → 管道中任何命令失败整个管道就失败为什么重要# 不加 set -e 的坑rm-rf$DIR/*# 如果 $DIR 为空变成 rm -rf /*# 加 set -eu 后DIRrm-rf$DIR/*# → 报错DIR: unbound variable立即退出-x 模式执行追踪#!/bin/bashset-x# 打印每条命令执行前的内容echoHello# 输出# echo Hello# HelloVARworldecho$VAR# 输出# VARworld# echo world# world临时对某段代码开启调试#!/bin/bashecho正常模式set-x# 这里的命令会被追踪complicated_functionsetxecho关闭追踪后-v 模式脚本行追踪bash-vscript.sh# 打印每行脚本内容执行前bash-xvscript.sh# 同时开启 -x 和 -v错误处理最佳实践trap 捕获退出信号#!/bin/bashset-euopipefail# 创建临时文件TMPFILE$(mktemp)# 无论脚本如何退出都执行清理cleanup(){localexit_code$?echo退出清理临时文件...rm-f$TMPFILEexit$exit_code}trapcleanup EXIT# 捕获错误打印出错位置err_handler(){echo错误发生在第$BASH_LINENO行$BASH_COMMANDecho退出码$?}traperr_handler ERR# 正常的脚本逻辑echo临时文件$TMPFILE# 哪怕脚本中途 CtrlC临时文件也会被清理自定义错误函数#!/bin/bash# 颜色输出RED\033[0;31mGREEN\033[0;32mYELLOW\033[1;33mNC\033[0m# 无颜色log_info(){echo-e${GREEN}[INFO]${NC}$*;}log_warn(){echo-e${YELLOW}[WARN]${NC}$*;}log_error(){echo-e${RED}[ERROR]${NC}$*2;}# 错误输出到 stderrdie(){log_error$exit1}# 使用log_info开始部署...command_might_fail||die部署失败请检查日志log_info部署成功日志记录同时输出到终端和日志文件#!/bin/bashLOG_FILE/var/log/myapp/deploy-$(date%Y%m%d_%H%M%S).logmkdir-p$(dirname$LOG_FILE)# 把所有输出stdout stderr同时写到文件和终端exec(tee-a$LOG_FILE)21echo 部署开始$(date)# 之后所有 echo 和命令输出都会同时出现在终端和日志文件里带时间戳的日志函数log(){echo[$(date%Y-%m-%d %H:%M:%S)]$*}log开始备份数据库...pg_dump mydbbackup.sql log备份完成变量调试# 查看变量值echo变量值${MY_VARQ}# Q 带引号输出显示特殊字符# 查看数组内容echo数组${MY_ARRAY[]Q}# 追踪变量赋值bash 4.4declare-tMY_VAR# 每次赋值时打印 trace# 检查变量是否为空if[[-z${MY_VAR:-}]];thenecho变量为空fi# 带默认值变量为空时用默认值NAME${USER_INPUT:-默认值}函数调试# 打印调用栈print_stack(){locali0localFRAMES${#FUNCNAME[]}echo调用栈从最内层到最外层for((i1;iFRAMES;i));doecho [$i]${BASH_SOURCE[$i]}:${BASH_LINENO[$i-1]}${FUNCNAME[$i]}done}my_function(){print_stack# 在函数里调用查看是谁调了我# ...}性能分析找出脚本哪里慢#!/bin/bash# 方法一time 命令time./long-script.sh# 方法二在脚本内计时start_time$(date%s%N)expensive_operationend_time$(date%s%N)elapsed$(((end_time-start_time)/1000000))echo耗时${elapsed}ms# 方法三PS4 变量配合 set -xexportPS4 [${BASH_SOURCE##*/}:${LINENO}] set-x# 现在追踪输出会显示文件名和行号常见陷阱和解决方法# 陷阱1空格问题FILEmy file.txtif[-f$FILE];then# 错$FILE 会被分成两个参数if[-f$FILE];then# 正确加引号# 陷阱2命令替换里的错误被忽略OUTPUT$(false_command)# 不会触发 set -eOUTPUT$(false_command)||exit1# 显式处理# 陷阱3grep 的退出码greppatternfile# 没找到返回 1会触发 set -egreppatternfile||true# 没找到也不退出# 陷阱4比较数字用 ((...))不要用 [ ]if[$NUM10];then# 错 是重定向会创建文件10if((NUM10));then# 正确算术比较# 陷阱5for 循环遍历文件forfilein$(ls*.txt);do# 错空格和换行会出问题forfilein*.txt;do# 正确直接用 glob使用 shellcheck 静态检查# 安装sudoaptinstall-yshellcheck# 检查脚本shellcheckmy-script.sh# 常见警告# SC2086: Double quote to prevent globbing# SC2006: Use $(...) instead of legacy ...# SC2046: Quote this to prevent word splitting# 在 VS Code 里安装 ShellCheck 插件实时检查一个完整的脚本模板#!/bin/bash# 描述做什么事# 用法./script.sh [参数]set-euopipefail# 常量readonlySCRIPT_DIR$(cd $(dirname${BASH_SOURCE[0]})pwd) readonly SCRIPT_NAME$(basename${BASH_SOURCE[0]}) readonly LOG_FILE/tmp/${SCRIPT_NAME%.*}-$(date%Y%m%d).log # 日志函数 log() { echo [$(date%H:%M:%S)]INFO$* | tee -a $LOG_FILE; } warn() { echo [$(date%H:%M:%S)]WARN$* | tee -a $LOG_FILE 2; } error() { echo [$(date%H:%M:%S)]ERROR$* | tee -a $LOG_FILE 2; } die() { error $; exit 1; } # 清理 cleanup() { log 脚本退出; } trap cleanup EXIT # 参数检查 usage() { echo 用法$SCRIPT_NAME[选项] echo -h显示帮助 exit 0 } while getopts h opt; do case$optin h) usage ;; *) die 未知参数 ;; esac done # 主逻辑 main() { log 开始执行... log 完成 } main $良好的 Shell 调试习惯能节省大量排查时间。set -euo pipefailtrap错误处理 shellcheck静态检查是生产环境 Shell 脚本的三件套。在雨云服务器 rainyuncom上跑的自动化脚本备份、部署、监控加上这些调试技巧出了问题马上定位注册填优惠码2026off领 5 折优惠券稳定可靠的服务器加上可靠的脚本是运维自动化的基础。