ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Shell脚本调试实战:从set命令到bashdb的四种高效排错方法

2026/8/18 3:32:10 拓冰建站 浏览量
Shell脚本调试实战:从set命令到bashdb的四种高效排错方法 1. 从“跑不通”到“看得清”为什么我们需要调试Shell脚本写Shell脚本尤其是Bash脚本对于运维、开发和数据分析的朋友来说几乎是家常便饭。你可能有过这样的经历花了半小时写好的脚本满怀期待地一运行结果要么悄无声息地结束要么抛出一堆看不懂的错误或者更糟——它“成功”运行了但把生产环境的数据给覆盖了。这时候你盯着屏幕上那几十行甚至几百行的代码感觉就像在玩一个没有攻略的“大家来找茬”游戏。这就是Shell脚本调试的价值所在。它不是一个可有可无的“高级技巧”而是保证脚本可靠性、可维护性的基本操作。很多人写脚本的习惯是“写一点跑一下”靠echo或者printf来打印变量值这当然是最朴素的方法。但当脚本逻辑复杂起来涉及条件判断、循环、函数调用和外部命令时这种“盲人摸象”式的调试就力不从心了。你需要一套系统的方法来透视脚本的执行过程看清每一行命令的输入、输出、返回值以及变量在每一个瞬间的状态。网络上搜索“shell脚本”时常伴随“shell脚本入门”、“shell中常见坑”、“-bash: xxx: command not found”这样的热词这恰恰说明了两个普遍痛点一是新手需要系统的学习路径二是老手也常被环境、语法细节和意料之外的行为绊倒。调试就是连接“写出来”和“跑正确”之间的那座桥。今天我们不谈高深的理论就聚焦于四种最实用、能立刻上手的Shell脚本调试方法让你下次再遇到脚本“罢工”时能从容地拿出“工具箱”而不是对着屏幕干瞪眼。2. 基础必备善用set命令家族让脚本“自曝”执行轨迹当你面对一个行为诡异的脚本第一步不是盲目地加echo而是应该让Shell自己告诉你它到底干了什么。Bash内建的set命令就是为此而生的它通过设置一系列选项options来改变Shell的行为其中几个用于调试的选项堪称神器。2.1-x选项透视脚本的每一步执行这是使用频率最高、效果最直接的调试选项。它的作用是在执行命令之前将该命令及其扩展后的参数打印到标准错误输出stderr。如何使用你可以在脚本开头直接加上set -x也可以在执行脚本时通过命令行传入bash -x your_script.sh。它长什么样假设你有一个简单的脚本test.sh#!/bin/bash set -x # 开启命令追踪 nameWorld echo Hello, $name files$(ls *.txt 2/dev/null) echo Found files: $files运行这个脚本你会看到类似这样的输出 nameWorld echo Hello, World Hello, World ls *.txt files echo Found files: Found files:每一行以开头的就是Bash打印出的即将执行的命令。注意观察nameWorld被直接打印。echo “Hello, $name“被打印为echo ‘Hello, World‘。这里可以看到变量$name已经被扩展替换为它的值World并且整个参数被加上了单引号。这是一个关键细节-x输出的是经过Shell扩展变量替换、命令替换、文件名通配等之后的命令。如果这里变量值是空的你就能立刻发现。$(ls *.txt …)这行出现了两个加号 ls *.txt。双加号表示这是在子Shell中执行的命令通常来自于命令替换$()或反引号 。这能帮你清晰地区分主脚本流程和子命令执行。命令ls *.txt的执行结果标准输出被赋值给了变量files。由于当前目录没有.txt文件ls命令可能产生错误但我们用2/dev/null将错误丢弃了所以files是空的。从 files这一行可以清楚地看到赋值结果。实战心得定位变量扩展问题当你怀疑变量没有正确赋值或包含特殊字符如空格、星号时-x的输出能一目了然地展示扩展后的实际值。理解命令执行顺序对于复杂的管道|、逻辑组合、||或者后台任务-x能清晰展示它们的执行流。关闭追踪在脚本中间你可以用set x关闭追踪只对你感兴趣的部分进行调试。2.2-e选项遇错即停避免雪崩默认情况下Bash脚本会忽略大多数命令的执行失败非零退出状态码继续往下执行。这常常是灾难的源头——一个前置命令失败了后面的命令基于错误的结果继续运行可能导致数据损坏。set -e选项改变了这一行为。它告诉Bash如果任何一条命令除了某些特例后面会讲以非零状态退出则立即终止整个脚本的执行。如何使用同样在脚本开头加set -e或运行bash -e your_script.sh。一个危险的反例#!/bin/bash # 没有 set -e cd /some/nonexistent/directory # 这条命令会失败cd 返回非零 rm -rf * # 天哪因为上一条命令失败当前目录没变这条命令会在你意想不到的目录执行 echo “Cleanup done?“ # 这行依然会执行制造一切正常的假象如果第一行是set -e脚本在cd失败的那一刻就会停止后面的rm和echo根本没有机会执行从而避免了一场可能的数据浩劫。注意事项与常见“坑”-e选项有一些众所周知的例外情况这也是很多人觉得它“不好用”的原因。理解这些例外至关重要管道中的命令在set -e模式下管道|中最后一条命令的失败才会导致脚本退出。例如grep ‘pattern‘ file.txt | wc -l如果grep没找到匹配项退出状态为1但wc成功退出状态为0整个管道被视为成功脚本不会退出。如果你希望管道中任何一部分失败都导致脚本退出需要设置set -o pipefail通常与set -e联用。条件判断中的命令在if、while、until的条件部分或者与、||组合的命令其失败不会被-e捕获。因为在这些语境下命令的失败正是流程控制所需要的逻辑判断依据。函数返回值如果一个函数被调用其内部某条命令失败但函数体末尾没有显式return非零值那么函数的退出状态是最后一条命令的状态。如果最后一条命令成功了即使中间有失败函数整体也被视为成功不会触发-e。最佳实践是在函数中对于可能失败的命令要显式地进行错误处理或返回明确的退出码。我的建议是对于重要的、尤其是可能产生副作用的脚本总是以set -euo pipefail这三连组合开头。-u我们接下来会讲-o pipefail解决了管道的异常处理问题。这为你的脚本建立了最严格的安全运行边界。2.3-u选项揪出未定义变量告别“空值”噩梦变量未定义或为空是Shell脚本中另一大类隐蔽的错误。默认情况下Bash会将未设置的变量视为空字符串。这听起来无害但在某些语境下是致命的。set -u选项的作用是当脚本尝试扩展一个未设置的变量时立即报错并终止脚本。一个典型的坑#!/bin/bash # 没有 set -u archive_name“backup-${date_stamp}.tar.gz“ # 假设 date_stamp 忘了定义 tar -czf “$archive_name“ /important/data # archive_name 变成了 “backup-.tar.gz“ # 结果/important/data 被压缩成了一个名字为 “backup-.tar.gz“ 的文件覆盖了可能已存在的同名空名文件如果设置了set -u脚本在尝试扩展${date_stamp}时就会报错bash: date_stamp: unbound variable并停止执行。如何处理可能为空的变量有时变量确实可能为空并且这是可接受的行为。此时你可以使用变量扩展语法来提供默认值这能与-u选项良好共存#!/bin/bash set -u # 如果 $USER_INPUT 未设置或为空则使用默认值 “default“ final_input“${USER_INPUT:-default}“ echo “Processing: $final_input“ # 或者仅当变量未设置时才使用默认值已设置但为空则保留空 final_input2“${USER_INPUT-default}“${VAR:-default}和${VAR-default}是Shell参数扩展的妙用前者在变量未设置或为空时用默认值后者仅在变量未设置时用默认值。熟练掌握它们能写出更健壮的脚本。2.4-v选项查看原始命令辅助-xset -v选项verbose会在读取命令后、执行命令前将命令的原始行未经扩展打印出来。它常与-x结合使用set -xv这样你既能看见原始代码-v又能看见扩展后实际执行的命令-x对于理解复杂的引用和扩展过程特别有帮助。组合使用示例在脚本开头写上set -euxo pipefail是一个在严肃脚本中非常推荐的组合。它的含义是-e 遇错即停。-u 使用未定义变量时报错。-x 打印执行轨迹。-o pipefail 管道中任何命令失败整个管道状态即为失败。这就像为你的脚本套上了一套“运行时检测与审计系统”虽然输出会变得冗长但在开发和调试阶段它能为你节省大量排查时间。3. 灵活控制使用trap捕获信号与退出实现调试钩子set选项是全局性的有时我们需要更精细的控制比如在脚本退出时、收到中断信号时自动执行一些清理或诊断操作。这时就要用到trap命令。trap允许你为Shell脚本捕获各种信号如EXIT、ERR、INT、TERM并指定处理函数。3.1 捕获EXIT无论成功与否善后必须做EXIT是一个伪信号在脚本退出无论正常退出还是因错误退出时触发。这是进行资源清理删除临时文件、释放锁、发送通知的绝佳位置。#!/bin/bash set -e temp_file“/tmp/myscript.$$.tmp“ # 使用 $$ 加入进程ID避免重名 cleanup() { echo “[DEBUG] 清理函数被调用。删除临时文件$temp_file“ rm -f “$temp_file“ # -f 避免文件不存在时报错 echo “[DEBUG] 清理完成。“ } # 注册 trap当脚本退出时执行 cleanup 函数 trap cleanup EXIT # 脚本主体逻辑 echo “开始处理...“ “$temp_file“ some_critical_operation # 如果 some_critical_operation 失败set -e 会导致脚本退出但 trap EXIT 会确保 cleanup 被执行。这样即使脚本中途崩溃临时文件也会被清理掉避免留下垃圾。3.2 捕获ERR定制化错误处理ERR是另一个伪信号当任何命令返回非零状态时在set -e生效的情况下就是导致脚本退出的那个错误触发。你可以用它来记录更丰富的错误上下文信息。#!/bin/bash set -e log_error() { local last_line“$1“ local last_cmd“$2“ local last_status“$3“ echo “[ERROR] 脚本 ‘${BASH_SOURCE[0]}‘ 在行号 ${last_line} 失败。“ echo “[ERROR] 执行的命令: ${last_cmd}“ echo “[ERROR] 退出状态码: ${last_status}“ # 可以在这里记录到文件、发送告警等 } # 使用 ‘trap ‘log_error $LINENO $BASH_COMMAND $?‘‘ ERR‘ 的格式来传递参数 trap ‘log_error $LINENO “$BASH_COMMAND“ $?‘ ERR # 一个有问题的命令 ls /nonexistent_directory # 这条命令会失败触发 ERR trap然后 set -e 导致脚本退出运行后你会看到比默认错误信息更详细的输出ls: cannot access ‘/nonexistent_directory‘: No such file or directory [ERROR] 脚本 ‘./test_err.sh‘ 在行号 18 失败。 [ERROR] 执行的命令: ls /nonexistent_directory [ERROR] 退出状态码: 2注意ERR陷阱在命令失败后、set -e导致退出前执行。BASH_COMMAND变量保存了当前正在执行或刚刚执行完的命令。3.3 结合调试在特定时机输出信息你还可以利用trap与DEBUG信号每个命令执行前触发来实现更复杂的调试跟踪但这通常过于冗长不如set -x直接。一个更实用的技巧是在脚本的不同部分设置不同的trap来改变调试行为。#!/bin/bash debug_start() { echo “ 进入复杂计算模块开启详细追踪 “ set -x } debug_stop() { set x echo “ 复杂计算模块结束关闭追踪 “ } # 在模块开始和结束时手动调用 debug_start # … 你的复杂计算代码 … debug_stop # 或者使用 trap 在函数进入和退出时自动设置需要 Bash 4.0 的 RETURN 信号trap提供了程序化的调试和错误处理入口让你的脚本不仅知道“怎么死”还能在“死前”留下遗言并整理好遗容。4. 神器登场Bash调试器bashdb给脚本单步执行的能力当你面对一个成百上千行、逻辑错综复杂的巨型脚本时前述方法可能依然让你感到吃力。你需要在循环的某一次迭代中暂停检查一堆变量的值或者想一步步跟踪一个函数的调用链。这时候你就需要一个真正的调试器——bashdb。bashdb是一个类似于gdb的Bash脚本调试器。它允许你设置断点、单步执行步入、步过、查看和修改变量、查看调用栈等等。4.1 安装与基础启动大多数Linux发行版可以通过包管理器安装# Debian/Ubuntu sudo apt-get install bashdb # RHEL/CentOS/Fedora (需要EPEL) sudo yum install bashdb # 或 sudo dnf install bashdb # macOS brew install bashdb安装后调试脚本的基本命令是bashdb your_script.sh [script_arguments]这会启动bashdb的交互式命令行界面。4.2 核心调试命令实战假设我们有一个脚本buggy.sh#!/bin/bash # buggy.sh function process_item() { local item“$1“ echo “Processing: $item“ # 假设这里有个复杂的处理我们想在此时暂停 result$(($item * 2)) # 注意如果 $item 不是数字这里会出错 echo “Result: $result“ } main() { local list(“1“ “2“ “not_a_number“ “4“) for i in “${list[]}“; do process_item “$i“ done echo “All done.“ } main “$“启动调试并设置断点$ bashdb buggy.sh bashdb0 break process_item # 在函数 process_item 入口处设置断点 Breakpoint 1 set in file buggy.sh, line 4. bashdb0 run # 运行脚本脚本会开始执行并在第一次调用process_item时暂停。单步执行与查看状态Stopped at line 4 in file buggy.sh 4: local item“$1“ bashdb1 print $1 # 查看函数的第一个参数 $1 1 bashdb2 next # 执行下一行步过不进入子函数或命令替换 Processing: 1 Stopped at line 7 in file buggy.sh 7: result$(($item * 2)) bashdb3 step # 执行下一行步入会进入命令替换或函数 ... (如果命令替换很复杂step会进入其子Shell) bashdb4 print item result # 查看变量 item 1 result bashdb5 continue # 继续运行直到下一个断点或脚本结束 Result: 2 Stopped at line 4 in file buggy.sh # 再次停在 process_item 入口处理第二个元素 4: local item“$1“关键命令列表break [location]: 设置断点。位置可以是行号break 10、函数名break process_item或文件:行号break buggy.sh:7。run [args]: 开始运行脚本。next/n: 执行下一行不进入函数或命令替换。step/s: 执行下一行进入函数或命令替换。continue/c: 从当前断点继续运行直到遇到下一个断点或脚本结束。print [expression]/p: 打印变量或表达式的值。如print i,print ${list[]}。list/l: 显示当前行附近的源代码。backtrace/bt: 显示当前的函数调用栈。watch variable: 设置监视点当变量被修改时暂停。quit/q: 退出调试器。4.3 实战调试定位算术错误在我们的例子中当循环处理到第三个元素“not_a_number“时$(($item * 2))会失败。使用bashdb我们可以精确地捕获这一刻。在process_item函数内可能出错的行第7行设置断点break 7。run后用continue快速跳到第三次循环的断点处。在断点暂停时使用print item查看会发现item“not_a_number“。此时执行next因为$((not_a_number * 2))是无效的算术表达式Bash会报错脚本可能会异常终止取决于是否设置了set -e。在bashdb中你能清晰地看到错误发生在哪一步以及当时的上下文。使用心得bashdb非常适合调试逻辑复杂、状态难以复现的脚本。它让你能“冻结时间”仔细检查程序状态。对于简单的语法错误或者变量未定义set -u和set -x通常更快。结合使用可以先通过set -x快速定位问题大致范围再用bashdb在关键区域进行精细的单步调试。bashdb本身也是一个Bash脚本它的命令和交互方式需要一点学习成本但一旦掌握调试效率会极大提升。5. 视觉化辅助IDE与编辑器的集成调试支持如果你不习惯命令行调试器或者脚本项目很大那么使用支持Shell调试的集成开发环境IDE或高级文本编辑器会是更舒适的选择。它们提供了图形化的断点点击、变量监视窗口、调用栈视图等。5.1 Visual Studio Code Bash Debug 插件VS Code 是目前最流行的选择之一。配置步骤安装插件在VS Code扩展商店搜索并安装 “Bash Debug” 插件。创建调试配置在项目根目录创建.vscode/launch.json文件或通过“运行和调试”面板创建。一个基本的配置如下{ “version“: “0.2.0“, “configurations“: [ { “type“: “bashdb“, “request“: “launch“, “name“: “Debug Shell Script“, “program“: “${file}“, // 调试当前打开的文件 “args“: [], // 脚本参数 “cwd“: “${workspaceFolder}“, “terminalKind“: “integrated“ // 使用集成终端 } ] }开始调试打开你的Shell脚本文件在行号左侧点击设置断点红点。然后按F5或点击调试面板的绿色播放按钮。VS Code会启动调试会话并在断点处暂停。你可以使用顶部的调试工具栏进行单步执行F10步过F11步入F5继续在侧边栏的“变量”窗口中查看和修改变量在“调用堆栈”窗口中查看函数调用关系。优势直观图形化界面断点、变量、堆栈一目了然。集成代码编辑、调试、版本控制都在一个环境中。强大支持条件断点、日志点、数据查看器查看数组、关联数组等高级功能。5.2 JetBrains IDE (如 IntelliJ IDEA Ultimate, PyCharm Professional)JetBrains的IDE对Shell脚本通过BashSupport或Shell脚本插件也有很好的调试支持配置方式与VS Code类似需要在运行/调试配置中选择正确的脚本解释器如/bin/bash并指定参数。5.3 其他编辑器像vim或emacs这类终端编辑器可以通过插件如vim-bash或realgud集成bashdb在编辑器内提供调试控制台和代码高亮联动适合习惯在终端内工作的开发者。选择建议如果你已经在使用VS Code进行其他开发那么用它来调试Shell脚本是最无缝的。如果你主要进行Java/Python开发并使用JetBrains全家桶那么使用IDEA或PyCharm的Shell调试功能可以保持环境统一。如果你工作在纯终端环境或服务器上那么熟练掌握bashdb命令行工具是必备技能。图形化工具降低了调试的门槛特别是对于变量状态的可视化展示能帮助你更快地理解数据流。但无论工具多么强大理解前面几节所述的基本调试原理如退出状态、变量扩展、信号处理才是根本。6. 调试思维与实战排查流程从“盲猜”到“科学”掌握了工具更重要的是建立一套调试思维。当脚本出错时一个高效的排查流程能让你事半功倍。6.1 第一步读懂错误信息Bash的错误信息有时很 cryptic隐晦。常见错误类型syntax error near unexpected token ‘xxx‘ 语法错误。检查括号、引号、分号、if/then/fi、do/done是否配对关键字前后是否有空格。command not found 命令未找到。检查命令拼写确认该命令是否已安装且在PATH环境变量中。特别注意热词中频繁出现的-bash: nginx: command not found、-bash: docker: command not found就是典型。这往往不是脚本问题而是环境问题。Permission denied 权限不足。检查文件/目录的执行x、读r、写w权限。使用ls -l查看。unbound variable 使用了未定义的变量在set -u模式下。检查变量名拼写确认变量在引用前已被赋值。binary operator expected 通常在[[ … ]]或(( … ))中出现。检查操作符两边的变量是否已定义且类型正确例如在算术比较中是否都是数字。6.2 第二步最小化复现不要试图在几百行的脚本里大海捞针。尝试创建一个最小的、能复现问题的测试脚本。剥离无关逻辑只保留导致错误的核心代码。这个过程本身常常就能帮你发现问题所在。6.3 第三步分层启用调试工具第一层静默观察。先不加任何调试选项运行看错误信息。第二层启用set -euxo pipefail。这能立刻捕获未定义变量和命令失败并给出更具体的失败位置。第三层启用set -x。如果问题依然不明打开执行追踪看命令的实际执行过程和变量扩展结果。这是解决“逻辑错误”和“数据错误”的利器。第四层使用trap或bashdb。对于复杂的流程控制、循环或函数调用问题在关键位置加入trap ‘echo “Line: $LINENO, Var$myvar“ 2‘ DEBUG或直接使用bashdb设置断点进行单步跟踪。6.4 一个综合排查案例脚本“静默”失败问题一个备份脚本backup.sh在夜间定时任务中运行日志显示它“成功完成”但实际没有生成备份文件。排查过程复现手动在命令行运行./backup.sh发现它瞬间结束没有输出也没有报错。第一层检查脚本开头没有set -e所以命令失败被忽略了。加上set -e再运行脚本立刻退出但依然没有错误信息这可能是因为错误输出被重定向了。第二层检查查看脚本发现有一行tar -czf backup.tar.gz /source/data 21 /dev/null。这里有一个经典的重定向错误21 /dev/null的意思是“将标准错误重定向到标准输出的当前位置终端然后将标准输出重定向到/dev/null”。所以错误信息stderr仍然打印到了终端但被我们手动运行时忽略掉了。而定时任务通常会捕获所有输出。正确的写法应该是tar -czf backup.tar.gz /source/data /dev/null 21先重定向stdout再重定向stderr到stdout的位置或者更好的 /dev/nullBash中重定向stdout和stderr。第三层检查修正重定向后再次运行set -e导致脚本退出并打印错误tar: /source/data: Cannot open: No such file or directory。原来源目录路径写错了根本原因脚本中使用了硬编码的绝对路径/source/data而在执行定时任务的环境如cron中这个路径不存在。cron的环境变量如PATH和当前工作目录可能与用户Shell环境不同。修复使用绝对路径时确保其存在且可访问。可以考虑在脚本开头用cd $(dirname “$0“)切换到脚本所在目录或使用配置变量。在关键操作前添加检查[[ -d “$SOURCE_DIR“ ]] || { echo “Error: SOURCE_DIR not found“ 2; exit 1; }。在定时任务中最好在脚本内显式设置关键环境变量或在crontab中定义。这个案例融合了错误处理set -e、重定向知识、环境差异和防御性编程。调试不仅是解决问题更是深入理解系统如何工作的过程。7. 进阶技巧与避坑指南最后分享一些在多年Shell脚本调试中积累的“血泪教训”和高效技巧。7.1 关于引用的“玄学”Shell中引用的使用是调试中最常见的痛点之一。变量引用永远加双引号“$var“。这是避免单词拆分word splitting和路径名扩展globbing引发意外错误的铁律。热词中shell中常见坑很多与此有关。命令替换$()也要加引号“$(cmd)“。除非你明确知道输出不需要分词。通配符和引号*.txt会在引号外被扩展在引号内就是字面量。如果你想把通配符作为参数传给命令如find . -name “*.sh“通配符必须在引号内由命令自己处理扩展。数组和引号“${array[]}“可以保留每个元素的完整性而“${array[*]}“会将所有元素合并成一个字符串。在for循环中遍历数组应使用for item in “${array[]}“; do …。7.2 测试与断言在脚本中嵌入简单的“断言”可以提前暴露问题。# 检查必需参数 : ${INPUT_FILE:?“Usage: $0 input_file“} # 如果 INPUT_FILE 为空则打印错误信息并退出 # 检查目录是否存在 [[ -d “$OUTPUT_DIR“ ]] || { mkdir -p “$OUTPUT_DIR“ || { echo “无法创建目录“ 2; exit 1; }; } # 检查命令是否可用 command -v jq /dev/null 21 || { echo “错误需要 jq 命令“ 2; exit 1; }7.3 日志是调试的朋友即使是简单的脚本有策略地记录日志也极其有用。不要只依赖echo。log() { local level“$1“ local message“$2“ # 带时间戳和进程ID的日志 printf “[%s] [PID:%s] [%s] %s\n“ “$(date ‘%Y-%m-%d %H:%M:%S‘)“ “$$“ “$level“ “$message“ 2 } log “INFO“ “脚本启动“ log “ERROR“ “文件 $file 不存在“将日志重定向到文件 “$LOG_FILE“ 21或系统日志logger以便后续分析。7.4 警惕外部命令的差异脚本可能在不同系统Linux, macOS, BSD上运行。像sed、grep、date等命令的选项可能有细微差别。使用#! /usr/bin/env bash指定解释器并在使用非POSIX标准特性时在脚本开头进行兼容性检查或使用feature_test。7.5shellcheck静态分析工具在运行脚本之前使用shellcheck一个Shell脚本静态分析工具检查代码。它能发现语法问题、常见的语义错误以及可能导致异常行为的代码模式。# 安装 # Debian/Ubuntu: sudo apt install shellcheck # macOS: brew install shellcheck shellcheck your_script.sh它会给出非常具体的警告和建议比如“变量未加引号”、“[和[[误用”、“echo可能不移植”等是提升脚本质量的必备工具。调试Shell脚本从依赖运气和echo到系统性地使用set、trap、调试器和科学的排查流程是一个从业者从新手走向熟练的标志。这些方法没有绝对的高下之分只有适合与否。简单的脚本set -x足矣复杂的项目bashdb或图形化调试器能大幅提升效率。最重要的是养成一种“防御性编程”和“透明化执行”的思维习惯让你的脚本不仅自己能跑通还能在出错时清晰地告诉你“我为什么倒下了倒在了哪里。”