当前位置: 首页 > news >正文

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 后DIR=""rm-rf"$DIR/"*# → 报错:DIR: unbound variable,立即退出

-x 模式(执行追踪)

#!/bin/bashset-x# 打印每条命令执行前的内容echo"Hello"# 输出:# + echo Hello# HelloVAR="world"echo"$VAR"# 输出:# + VAR=world# + echo world# world

临时对某段代码开启调试:

#!/bin/bashecho"正常模式"set-x# 这里的命令会被追踪complicated_functionset+xecho"关闭追踪后"

-v 模式(脚本行追踪)

bash-vscript.sh# 打印每行脚本内容(执行前)bash-xvscript.sh# 同时开启 -x 和 -v

错误处理最佳实践

trap 捕获退出信号

#!/bin/bashset-euopipefail# 创建临时文件TMPFILE=$(mktemp)# 无论脚本如何退出,都执行清理cleanup(){localexit_code=$?echo"退出,清理临时文件..."rm-f"$TMPFILE"exit$exit_code}trapcleanup EXIT# 捕获错误,打印出错位置err_handler(){echo"错误发生在第$BASH_LINENO行:$BASH_COMMAND"echo"退出码:$?"}traperr_handler ERR# 正常的脚本逻辑echo"临时文件:$TMPFILE"# 哪怕脚本中途 Ctrl+C,临时文件也会被清理

自定义错误函数

#!/bin/bash# 颜色输出RED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[1;33m'NC='\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).log"mkdir-p"$(dirname"$LOG_FILE")"# 把所有输出(stdout + stderr)同时写到文件和终端exec>>(tee-a"$LOG_FILE")2>&1echo"=== 部署开始:$(date)==="# 之后所有 echo 和命令输出都会同时出现在终端和日志文件里

带时间戳的日志函数

log(){echo"[$(date'+%Y-%m-%d %H:%M:%S')]$*"}log"开始备份数据库..."pg_dump mydb>backup.sql log"备份完成"

变量调试

# 查看变量值echo"变量值:${MY_VAR@Q}"# @Q 带引号输出,显示特殊字符# 查看数组内容echo"数组:${MY_ARRAY[@]@Q}"# 追踪变量赋值(bash 4.4+)declare-tMY_VAR# 每次赋值时打印 trace# 检查变量是否为空if[[-z"${MY_VAR:-}"]];thenecho"变量为空"fi# 带默认值(变量为空时用默认值)NAME="${USER_INPUT:-默认值}"

函数调试

# 打印调用栈print_stack(){locali=0localFRAMES=${#FUNCNAME[@]}echo"调用栈(从最内层到最外层):"for((i=1;i<FRAMES;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 -x)exportPS4='+ [${BASH_SOURCE##*/}:${LINENO}] 'set-x# 现在追踪输出会显示文件名和行号

常见陷阱和解决方法

# 陷阱1:空格问题FILE="my file.txt"if[-f$FILE];then# 错!$FILE 会被分成两个参数if[-f"$FILE"];then# 正确:加引号# 陷阱2:命令替换里的错误被忽略OUTPUT=$(false_command)# 不会触发 set -e!OUTPUT=$(false_command)||exit1# 显式处理# 陷阱3:grep 的退出码grep"pattern"file# 没找到返回 1,会触发 set -egrep"pattern"file||true# 没找到也不退出# 陷阱4:比较数字用 ((...)),不要用 [ ]if[$NUM>10];then# 错!> 是重定向,会创建文件"10"if((NUM>10));then# 正确:算术比较# 陷阱5:for 循环遍历文件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 pipefail+trap错误处理 +shellcheck静态检查,是生产环境 Shell 脚本的三件套。在雨云服务器 rainyun+com上跑的自动化脚本(备份、部署、监控)加上这些调试技巧,出了问题马上定位,注册填优惠码2026off领 5 折优惠券,稳定可靠的服务器加上可靠的脚本,是运维自动化的基础。

http://www.jsqmd.com/news/829543/

相关文章:

  • 如何快速清理Zotero重复文献:智能合并工具完整指南
  • 瑞萨CS+ for CC实战:手把手教你配置BootLoader双程序地址与HEX文件合并(附避坑指南)
  • mysql在事务中执行DDL的后果_MySQL 8.0之前的限制
  • Hailo-8边缘算力实战:从模型编译到Python流式推理全解析
  • 3步掌握CompressO:彻底解决大文件存储难题的智能压缩方案
  • HTTPCanary Magisk模块技术解析:Android HTTPS抓包的系统级解决方案
  • 从仿真到代码:手把手教你用Python+MoveIt API控制UR5机械臂完成多物体抓取搬运
  • SLO-Warden:云原生时代SLO自动化管理的工程实践
  • Excalidraw终极指南:快速掌握免费开源虚拟白板的完整使用技巧
  • SpringCloud Feign服务调用超时,熔断机制失效
  • 从零构建本地化智能家居大脑:Home Assistant实战指南
  • Claude Code出质量事故了?Anthropic发了一篇有诚意的复盘|AI新岗位FDE爆火
  • ComfyUI-AnimateDiff-Evolved:五分钟快速掌握AI动画生成终极指南
  • 3秒找到任何文件:FSearch让Linux文件搜索变得如此简单
  • 脱离 Spring Boot 官方 Parent 之后,我才弄懂 Maven 的 -D 参数真相
  • ChanlunX缠论插件:5分钟实现专业缠论分析的智能解决方案
  • 对比官方价格Taotoken活动价在模型调用上的成本优势
  • 告别显示器!树莓派5无屏启动与远程配置全攻略(最新Raspberry Pi OS,含网络配置与VNC/SSH一键脚本)
  • 算法竞赛中的‘暴力美学’:以CCPC吉林赛F题(Queue)为例,聊聊小范围数据下的巧妙解法
  • 稀有气体成键新解:从惰性到化合
  • 显卡驱动清理终极指南:Display Driver Uninstaller 高效解决方案
  • 别再死记硬背了!用Protege从零构建一个电影知识图谱(附完整OWL文件)
  • 工业设备人机交互实战:串口屏在激光清洗设备中的应用与优化
  • Need is all you need:AI接手Coding后,程序员最值钱的能力只剩这一项?
  • Hermes Agent工具连接Taotoken大模型服务的配置指南
  • 别再只会用PWM了!S32K FTM输入捕获模式精确测量脉冲宽度与频率(附代码)
  • 如何高效管理魂系游戏模组:ModEngine2实战指南与最佳实践
  • C++ mutable关键字:逻辑常量性与线程安全缓存实战解析
  • 开源机械爪资源宝库:从入门到进阶的完整实践指南
  • 电商冷启动实战:0.01元引流、50单破局、0差评与8.8%转化率