模块S12 调试、测试与排错篇号S12-03 / 42预计阅读55 分钟主线Bash文章目录本篇目标30 秒速览正文1. 排错六步固定套路2. 先记什么现象清单3. 最小复现4. 二分定位注释法5. 与 set -x 配合S12-016. set -e 专项7. 变量与引号类S02、S038. 环境差异对比9. 样例走读一段「莫名退出」10. 样例二分定位演示11. 不要做的事12. 工具链回顾S12 模块读脚本检查清单练习判断题实操题 1实操题 2读脚本题S12 模块小结下一篇预告本篇目标掌握脚本排错的固定流程从现象到根因用最小复现缩小战场用注释/二分定位失败命令配合S12-01-x、S12-02 ShellCheck、S04$?。能对一段「能跑但结果错 / 莫名退出」的脚本说出下一步该查什么。本篇为S12 模块收尾。30 秒速览先复现再改记录命令、环境、退出码、stderr。最小复现删到仍能失败的最短脚本或最短调用。二分注释掉一半逻辑看失败是否还在缩小范围。set -e失败时用-x看最后一条命令S12-01。对比同一脚本在「好环境 / 坏环境」差哪些变量、路径、权限。别同时改多处一次只验证一个假设。正文1. 排错六步固定套路步动作1. 描述现象期望什么实际什么退出码2. 稳定复现同一命令连跑两次是否一致3. 收集证据stderr、$?、bash -x日志4. 最小复现删无关代码/数据5. 二分定位注释/拆分找到第一条坏命令6. 修复验证改一处跑回归ShellCheck 原失败用例读他人脚本卡住时同样从2→5走不必先读懂全文件。2. 先记什么现象清单./deploy.sh-frelease.tar.gzechoexit$?记录项示例完整命令行./deploy.sh -f x.tar.gz工作目录pwdShell 版本bash --version退出码echo $?stderr 最后一行not found: ...相关环境变量env | grep DEPLOY「在我机器上能跑」→ 差在环境、路径、数据不是玄学。3. 最小复现原则能删就删直到再删就不失败了。# 原200 行 deploy.sh 十个依赖文件# 缩只保留失败路径#!/usr/bin/env bashset-euopipefailartifact$1[[-f$artifact]]||exit1sha256sum-c${artifact}.sha256# 假设死在这里若sha256sum -c单独对两个文件仍失败 → 问题在文件内容/配对不是前面getopts。数据也要缩小大日志抽失败前后 20 行单独文件测。4. 二分定位注释法S02-02行首#可禁用整条命令。排错时故意用# 怀疑后半段# step3# step4step5# 仍失败 → 问题在 step5 或之前未注释的 step1-2对半注释[ step1 step2 step3 | step4 step5 step6 ] 失败 → 注释 step4-6仍失败 → 坏在 step1-3 → 再对半 step1-2 vs step3比「一行行试」快适合S01-04set -e中途退出、不知哪条命令杀脚本。注意注释set -e块时可能改变退出行为二分阶段可临时set e只为探路定位后再恢复。5. 与set -x配合S12-01定位到大概区块后set-x# 可疑 10 行setx看最后一条命令下一条未出现 失败或set -e退出点。exportPS4(${LINENO}): set-x管道S09-02 false | true后有无 echo after结合pipefail。6.set -e专项现象查什么无报错突然退出-x最后一条grep无匹配退出 1以为成功其实失败if cmd与裸cmd区别管道前半失败pipefail开没开# 探路临时看每条退出码定位完删掉sete step1;echos1$?step2;echos2$?set-e7. 变量与引号类S02、S03症状路径含空格断成两段、空变量删错文件。# 临时调试勿提交set-xechofile[$file]ls-l--$filesetxShellCheck SC2086S12-02静态先扫一遍。8. 环境差异对比# 好 / 坏各跑一次bash-xscript.sh2good.trace# 换机器或换用户bash-xscript.sh2bad.tracediffgood.trace bad.trace常见差因差因检查未 source env函数/变量未定义cwd 不同相对路径./config权限[[ -r ]]BusyBox vs GNUgrep/sed选项旧 Bash[[ ~ ]]、数组9. 样例走读一段「莫名退出」#!/usr/bin/env bashset-euopipefailCONFIGapp.conf[[-f$CONFIG]]||exit1source$CONFIGOUT_DIR/var/outmkdir-p$OUT_DIRgrep-q^enabled1$CONFIG||exit0deploy_all$OUT_DIRechodone现象无doneecho $?为 0。走读grep -q ... || exit 0enabled1不存在时整脚本成功退出 0——不是 crash是逻辑提前 exit 0。若用户以为「没部署就是 bug」→ 查配置行不是 bash 坏。若source失败set -e→-x停在source查app.conf语法。deploy_all未定义set -u→ 报错在调用前。教训先分清exit 0 提前结束vs非 0 失败vsset -u报错。10. 样例二分定位演示#!/usr/bin/env bashset-euopipefail prepare fetch_data validate transform upload notifyechook现象无ok退出码 1。轮次操作结果1注释upload notify仍失败 → 坏在 preparetransform2注释transform validate成功 → 坏在 validate 或 transform3只跑validate定位 validate 内命令再配合validate函数内set -x。11. 不要做的事不要原因未复现就改十处无法确认哪处有效产线长期set -x含密钥S12-01忽略$?S04-01只靠echo刷屏应缩小范围把 **12. 工具链回顾S12 模块工具何时ShellCheck改代码前/CIbash -n语法set -x/ PS4运行时展开最小复现 二分不知哪行S06-03 读判断链逻辑走岔S14-03更长真实脚本整体拆读脚本检查清单失败时是否记录了退出码与完整命令能否写出最小复现调用set -e退出是否用-x对过最后一条命令是否区分exit 0 提前结束与失败修完后是否用原失败用例回归练习判断题最小复现要求保留「能触发 bug 的最少代码」。二分排错时一次注释掉约一半逻辑是合理策略。grep -q无匹配在set -e下可能导致脚本退出。bash -x与 ShellCheck 解决同一类问题。排错时应同时改环境变量、脚本逻辑和数据文件以节省时间。参考答案对。对。对裸写grep -q时。错动态 vs 静态。错一次一个变量。实操题 1下面脚本预期打印done实际没有。原因是什么不运行也可分析#!/usr/bin/env bashset-euopipefailMODE${1:-}[[-n$MODE]]||exit2grep-q^${MODE}1$settings.conf||exit0run_taskechodone调用./task.sh productionsettings.conf里只有production0。参考答案grep无匹配 →|| exit 0→ 脚本以 0 成功退出不执行run_task与echo done。不是set -e崩溃是业务逻辑把「未启用」当成正常结束。修复需改需求应用exit 1、或区分「配置不存在」与「未启用」。实操题 2写出用二分缩小main中失败范围的第一步注释策略不必真跑。main(){step_a step_b step_c step_d step_e}已知main失败且无done打印。参考答案第一步注释step_cstep_dstep_e或后半只留step_astep_b若仍失败则坏在前半若成功则坏在step_c/d/e之一再对半。读脚本题deploy.sh在 CI 失败、本机成功。列出你会对比的4 项环境信息。参考答案示例bash --versionpwd/工作目录关键环境变量PATH、HOME、业务变量文件存在与权限配置、锁、输入数据用户身份依赖命令版本shellcheck/sha256sum等。S12 模块小结篇号主题S12-01set -x、PS4动态跟踪S12-02ShellCheck静态检查S12-03最小复现、二分、现象清单下一模块S13讲POSIX 与 Bash 可移植。下一篇预告S13-01《POSIX sh 与 Bash语法对照表》—[[vs[、pipefail、哪些写法不能写进#!/bin/sh。