说起来,我在刚开始写一些自动化脚本或者小工具的时候,对这块儿真没太当回事。那时候,我写个脚本,跑起来,如果失败了,它自己退出了,我就觉得“,失败了呗”,然后直接去看命令行输出找错误。很多时候,脚本里直接就扔个exit 1,感觉挺干脆的。
刚开始的时候,遇到的问题还比较简单,脚本就那么几行,一眼就能扫到底。但慢慢地,手里的活儿越来越复杂,写的脚本也越来越长,调用来调用去的,一个脚本可能要依赖好几个其他的脚本或者外部程序。这时候,exit 1就开始给我找麻烦了。
第一次栽跟头:静默失败
我记得特别清楚,有一次我写了个定时任务,每天凌晨跑,去抓取一些数据,然后做个简单的处理。我的想法是,如果抓取数据这步出了问题,脚本就exit 1,然后整个任务就停了,我第二天醒来一看日志,发现没跑成功就去排查。
结果?有好几天,我发现数据没更新,但日志里啥也没显示,任务状态也是“成功”!我就纳闷了,跑过去一看,诶,我的脚本根本没跑完,在某个地方就提前退出了。但是因为我没把错误信息输出也没把退出码处理导致父进程以为我成功了,也没个响动,就那么静悄悄地失败了。
那时候我真是抓狂了,排查了半天才找到问题。原来,我的一个子脚本在某个特定条件下,会不声不响地exit 1,然后主脚本就接收到这个信号,自己也退了。最要命的是,我压根儿没把子脚本的错误信息转发出来,主脚本也没捕获,所以整个流程就跟没发生过一样,但结果就是错的。
痛定思痛:摸索定位问题的方法
小编温馨提醒:本站只提供游戏介绍,下载游戏请前往89游戏主站,89游戏提供真人恋爱/绅士游戏/3A单机游戏大全,点我立即前往》》》绅士游戏下载专区
经过那次,我才意识到,exit 1可不是随便一扔就行的,它是一个信号,得把它用我就开始琢磨,遇到这种问题到底该怎么办?
第一招:加日志,使劲加日志!
痛定思痛后,我做的第一件事就是:在任何可能导致exit 1的地方前面,都给我把当时的状况打印出来! 就像这样:
- “现在要开始处理文件A了……”
- “尝试连接数据库……”
- “变量`fileName`现在的值是`/path/to/my/*`”
- “文件`/path/to/my/*`不存在,即将退出!”
我用echo也用Python的print也反正就是要让脚本“说话”。特别是,在即将exit 1之前,一定要把导致退出的具体原因给描述清楚。比如,如果是因为文件找不到,那就打印“错误:文件[文件路径]不存在,程序终止。”
这样一来,再次遇到问题,我至少能在日志里找到蛛丝马迹,知道它是在哪一步出的错,大概是什么原因。
第二招:理解退出码,并利用它
后来我又深入研究了下,原来在Linux或者类Unix系统里,程序的退出码是个很有用的东西。exit 0通常代表程序成功执行,而任何非零值都代表程序执行过程中遇到了问题。我之前就是没把这个1当回事儿。
我才明白,我的主脚本可以通过检查子脚本的退出码来判断它是否成功了。在shell脚本里,可以用这个特殊变量来获取上一个命令的退出码。我开始尝试这么写:
./sub_*
if [ $? -ne 0 ]; then
echo "错误:子脚本sub_*执行失败!" >&2
exit 1
fi
这样,一旦子脚本exit 1,主脚本就能捕获到,并且自己也会带上错误信息退出,不会再静默失败了。我甚至会根据不同的错误类型,让子脚本返回不同的非零退出码(比如文件不存在就exit 2,权限不够就exit 3),这样主脚本就能更细致地判断是什么问题了。
第三招:错误信息输出到标准错误流
再后来我又学到了一个更“规范”的做法,就是把正常的输出信息打到标准输出(stdout),而把错误信息打到标准错误(stderr)。在shell里,就是用>&2来重定向。这个操作,对我之前的日志排查简直是神来之笔。
echo "正常处理中的信息"
echo "错误:处理XXX失败,原因:YYY" >&2
exit 1
这样有个特别大的好处:当我在查看日志或者用管道符处理脚本输出的时候,我可以很方便地把正常的输出和错误信息分开。比如,我只想看错误,就可以把标准输出重定向到/dev/null:./my_* > /dev/null,这样屏幕上就只剩下错误信息了。
我的心得:exit 1 是个好信号
通过这一系列的折腾和学习,我现在再遇到exit 1,心态就完全不一样了。它不再是我的“拦路虎”,而是一个非常有用的“信号灯”。
现在我写脚本或者程序,都会特别注意错误处理:
- 预判可能出错的地方:在这些地方做好检查,比如文件是否存在,网络是否畅通,参数是否合法。
- 详细的错误日志:一旦出错,不光要
exit 1,还得把具体是什么错了,当时的上下文是什么,清清楚楚地打印出来。 - 合理的退出码:如果可能,用不同的非零退出码来区分不同的错误类型,方便上层调用者判断。
- 错误信息输出到标准错误流:让错误信息“有自己的地盘”,不跟正常的输出混淆。
我深刻体会到,一个健壮的程序,不是说它永远不出错,而是它在出错的时候,能清清楚楚地告诉我它出了什么错,为什么出错,这样我才能快速定位和解决问题。遇到exit 1别怕,它不是敌人,而是个信号灯,告诉你哪里可能出了问题,好好利用它,你就能写出更稳定、更靠谱的程序。



