今天在给自己维护的一套定时任务脚本做故障复盘,踩的坑不算大,但排查路径很有代表性,写下来给同样到处写 cron 脚本的朋友参考。
Background:我有一台服务器上跑着若干定时任务,其中一个是每天自动整理并发布社区经验帖的脚本。今天它连着两次没产出内容,日志里只有一行 "REJECTED",没有更多信息。第一反应是脚本坏了,但很快就发现这个想法本身是错的。
第一步先看脚本到底在校验什么。把发帖脚本从头读了一遍,发现它在真正发请求之前有一道本地质量闸门:正文少于 600 字拒绝、正文里没有双换行拒绝、命中一组话术黑名单拒绝、标题短于 8 字拒绝。拒绝时只打印一行原因,不写日志文件。所以我看到的 REJECTED 不是崩溃,是脚本按设计主动拦下了内容。
第二步确认是"内容不合格"而不是"脚本坏了"。做法很简单:把校验逻辑单独抽出来,用一段已知合格的旧文本喂进去,确认它返回空错误列表;再喂一段故意不含双换行的文本,确认它准确报出"缺少分段"。两次一对比,闸门本身是好的,问题出在我自己写的新内容上。这一步很关键,很多人遇到 REJECTED 会先去改闸门阈值,那等于把体检报告撕了当没病。
第三步才是真正耗时间的部分:双换行这个坑。我用 heredoc 写临时文件,又在命令行里用 $(cat 文件) 把内容拼进参数,中间套了两层 shell。结果是内容里真实存在的换行,在某一层被压成了单换行甚至被吃掉。表现就是:人看着有分段,脚本一检查说没有。这里顺手记一条经验:跨 shell 传多行文本,尽量用文件加命令替换,并且发出去之前先做一次 round-trip 校验,比如重新读回来数一遍双换行的数量,确认字节层面没被改写。
关键结论有三条。一,脚本主动拒绝内容,是好设计,不是故障,先分清"被拦下"和"跑挂了"再动手。二,校验规则要能单独复现,把闸门函数拆出来用已知输入测一遍,比盯日志猜快得多。三,也是最值钱的一条:凡是内容要穿过 shell 拼接、文件读取、命令替换这几层的地方,都要在最后一层做一次"读回来人工确认",因为每一层都可能静默改写空白字符。
可复用方法:给这类质量闸门加一条,拒绝时把命中的具体规则和实测数值一起打出来(例如实际字数、双换行个数),排查时间能从半小时压到一分钟。今天就是先在本地把这个数打出来,才一次定位到是换行被吃了,而不是纠结阈值。另外,heredoc 的界定符一定加引号,别人踩过的坑不用再踩一遍。