先说个我亲历的场景:某天早上一到工位,运维群里就炸了——昨晚的数据库备份没跑,备份目录空空如也。我第一反应是执行 crontab -l 看配置,任务明明还在,语法也对,手动执行脚本也正常,但定时就是不触发。那天花了将近半天才定位到问题,最后发现是脚本里的环境变量在 cron 环境下变了,而整个排查过程其实可以压缩成一套固定流程。
这篇文章就围绕“crontab 定时任务不生效”这件事,把我在实际工作中反复用到的排查方法整理清楚:三步核查加日志抓取。三步分别是看服务与配置、看环境与权限、看时间语义,每一步都告诉你为什么要这么查、具体查什么、用什么命令验证。内容主要面向被定时任务坑过的运维、后端开发和自建服务器的个人站长,看完可以直接照着一路排查下去。
1. 先建立排查框架:crontab 不执行,故障一共就三层
很多人排查 crontab 问题时习惯东敲一下西试一下:先改个时间,再换条命令,最后发现还是不行,又去重启服务。这么做不是不对,而是没有框架,容易白折腾。把 crontab 的执行链路拆开看,问题其实只可能出在三个层面上。
一条定时任务从配置到执行,完整的路径是这样的:crond 守护进程启动时读取所有用户的定时任务文件,把每条任务加载进调度列表;到了匹配的时间点,crond fork 出一个子进程来执行任务里写的命令;命令运行时的输出会通过邮件或重定向的方式落盘。这条链路上任何一个环节出了问题,任务都会“不生效”。
对应下来就是三个故障层。
第一层是配置层:任务文件没被 crond 正确读取,或者读到了但语法写错,导致要么解析失败、要么时间条件永远不匹配。第二层是执行层:crond 确实按时把任务启动了,但拉起来的脚本自己跑挂了,比如环境变量不对、权限不足、命令找不到。第三层是调度层:crond 根本没在正确的时间点触发任务,比如服务没运行、服务器时区不对、系统时间被 NTP 大幅校正后跳过了触发窗口。
可以这么理解:crontab 像一只闹钟。闹钟没响,要么是闹钟本身坏了(调度层有问题),要么是你把闹钟定错了时间(配置层有问题),要么是闹钟响了你压根听不见(执行层和环境有问题)。这三个层面不是并列关系,而是存在排查顺序的。
我建议的顺序是:先查配置层,再查执行层,最后查调度层。为什么不能反过来?因为调度层的“时间对不对”依赖于配置层的“任务是否被正确加载”,如果配置本身有问题,你查时区、查服务状态都是白费劲。而执行层的问题,比如脚本里的命令找不到,通常要确认配置语法没问题、任务确实被触发之后才有意义。换句话说,三步核查是一层一层往下剥的,顺序错了,你会反复在原地打转。
2. 第一步核查:服务状态、配置语法与任务落盘
2.1 服务状态排查:crond 到底跑没跑
这是最容易被忽略的一步。很多服务器在迁移、重装或者被维护之后,crond 服务压根没有启动,或者开机没有设为自动启动。你前面配置写得再对也没有用,因为指挥中心根本没上班。
检查命令很简单,根据自己的发行版选择即可:
# CentOS / RHEL 系 systemctl status crond # Debian / Ubuntu 系 systemctl status cron重点关注 Active 这一行,如果是 active (running),说明服务正常;如果是 inactive (dead) 或者 failed,那就先启动服务再谈其他:
systemctl start crond systemctl enable crondenable 这一步很多人会漏。它解决的是重启后服务是否自动拉起的问题,如果你只执行了 start,那么服务器一旦重启,定时任务又会集体“罢工”。我遇到过一个比较迷惑的场景:任务白天都正常,唯独服务器重启当天夜里的任务丢了,后来才发现是服务没有 enable。这种情况在云主机和虚拟机上特别常见,镜像初始化的脚本没跑全套。
2.2 配置语法过关:五个时间字段和三个隐藏坑
服务没问题之后,用 crontab -l 查看当前用户的任务定义,逐条检查语法。
标准格式是五个时间字段加一条命令,顺序不能乱:
| 字段 | 含义 | 取值范围 |
|---|---|---|
| 第1位 | 分钟 | 0-59 |
| 第2位 | 小时 | 0-23 |
| 第3位 | 日 | 1-31 |
| 第4位 | 月 | 1-12 |
| 第5位 | 周 | 0-7(0和7都代表周日) |
这五个字段里藏了三个非常容易踩的坑。
第一个坑是 % 号。crontab 里 % 会被当作换行符处理,而换行符后面的内容会被当作额外输入传递给命令。有人写备份脚本时想给日志文件加上时间戳,在 crontab 里直接写了 date +%Y%m%d,结果执行的时候命令被截断,日志文件名变成了一堆乱七八糟的东西。解决方案是写成人 format 必须转义:date +%Y%m%d;或者更稳妥一点,把时间格式的计算放到脚本内部去,不要在 crontab 行里出现 %。
第二个坑是对日字段和周字段关系的误解。五字段里日和周同时设置时,crontab 的语义是“满足任意一个就执行”,也就是 OR 逻辑,后面我会专门展开讲,这里先记住:如果你想表达“每个月的1号和15号,且这一天恰好是周五才执行”,直接用标准 crontab 写是写不出来的。
第三个坑是命令字段里包含了空格或特殊字符时的处理。crontab 的每一行以换行符分隔,命令内部按 shell 方式解析。如果命令路径里带有空格,建议用引号包住或用符号链接指向无空格的路径;如果命令里有多条子命令,需要写成 bash -c "命令1; 命令2" 这样的形式,避免 parse 阶段的歧义。
2.3 任务落盘确认:配置真的被 crond 读到了吗
语法看起来没问题,任务也在,但还有一个隐蔽问题:你编辑的账目和 crond 实际读取的账目是不是同一个,配置是否真的落到了正确的文件里。
用户级定时任务存放的位置是 /var/spool/cron/ 目录下以用户名命名的文件,比如 root 用户对应 /var/spool/cron/root。通过 crontab -e 修改配置时,命令会自动帮你写好这个文件并通知 crond 重新加载。但如果你手贱直接去编辑器里改了 /var/spool/cron/root,或者改完又手动删了什么,就可能出现配置不生效或者任务丢失的情况。
另一个容易混淆的是系统级 cron 配置文件。/etc/crontab 和 /etc/cron.d/ 目录下的文件是系统级任务,它们的格式比用户级多一个“执行用户”字段:
# 用户级(crontab -e 编辑) 30 2 * * * bash /opt/backup.sh # 系统级(/etc/crontab 或 /etc/cron.d/ 下的文件) 30 2 * * * root bash /opt/backup.sh如果你把用户级的写法直接丢进 /etc/cron.d/,crond 会把 root 当成命令的一部分去执行,任务自然失败。
落盘确认的做法很简单,确保当前用户正确:
crontab -l ls -l /var/spool/cron/如果 /var/spool/cron/ 下找不到你期望的文件,说明配置并没有真正保存成功。另外还要确认 /etc/cron.allow 和 /etc/cron.deny 这两个文件是否存在,它们决定了哪些用户有权限使用 crontab。如果用户在 deny 列表里或者不在 allow 列表里,任务同样不会被执行,连报错都看不到。
3. 第二步核查:环境变量、权限与静默失败的高发区
配置层确认无误后,问题就进入执行层。这一层是 crontab 故障里最阴险的部分,因为它往往表现为“手动执行完全正常,定时执行却什么也没发生”。这也是我在开头提到的那个备份事故的真正原因所在。
3.1 PATH 差异:定时执行和手动执行的第一个不同
手动在终端执行脚本时,你继承的是登录 shell 的环境变量,PATH 里通常包含 /usr/local/bin、/usr/bin、/bin,甚至还有用户自定义的 bin 目录。但 crond 在 fork 子进程时,使用的 PATH 极其精简,默认通常只有 /usr/bin:/bin。
这就导致一种非常经典的故障:脚本里写 python3 xxx.py,你手动敲没问题,因为你的 shell 能找到 python3;但 cron 环境里 PATH 没有包含 python3 所在的 /usr/local/bin,于是任务启动后直接报 command not found,脚本在第 3 行就退出了。
解决方法有三种,按推荐程度排序:
第一种,在脚本开头显式声明 PATH:
#!/bin/bash export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:$PATH"第二种,在 crontab 配置顶部统一声明环境变量:
PATH=/usr/local/bin:/usr/bin:/bin 30 2 * * * bash /opt/backup.sh第三种,在 crontab 行内直接使用命令的绝对路径,比如把 python3 换成 /usr/local/bin/python3。
我的经验是:凡是需要交给 cron 执行的脚本,一律在脚本开头设置 PATH,不要依赖系统默认值。因为你永远不知道这台服务器未来会不会调整环境,脚本把话说死,才能保证两三年后还能正常跑。
3.2 脚本权限与解释器:被忽略的执行前置条件
脚本文件本身的权限也是高发问题。很多人写了脚本,用 chmod 加了 x 权限,但任务仍然跑不起来。常见的情况是脚本所有者是 A 用户,而 crontab 任务却注册在 B 用户下,导致 B 用户没有执行权限,或者没有对脚本日志文件的写权限。
这里有一个我踩过好几回的坑:用 crontab -e 编辑任务时,不小心把任务写进了 root 的 crontab,但脚本存放在普通用户的家目录下,权限是 700,只有该用户自己能读。root 去执行时,文件能不能访问其实还好说,真正麻烦的是脚本内部写日志时,如果日志路径也在这个 700 的目录下,root 写不进去,任务就静默失败。
另一个和权限相关的坑是 CRLF 换行符。Windows 下编辑过的脚本文件传到 Linux 上,行尾会带有 \r,而 shebang 行就会变成 #!/bin/bash\r,执行时报 “bad interpreter: /bin/bash^M” 之类的错误。定时任务日志里可能只显示一个失败状态,不告诉你具体原因。
处理 CRLF 的办法:
sed -i 's/\r$//' your_script.sh或者用 dos2unix your_script.sh。这个细节很不起眼,但在排查 cron 问题时真的会遇到。
还有一个实用建议:如果不是特殊原因,crontab 里调用脚本时直接写 bash /path/script.sh,而不是 ./script.sh。前者不要求脚本有可执行权限,还绕开了 shebang 解析可能带来的问题,跑起来更稳。
3.3 用户权限与文件依赖:外部环境不齐全
脚本执行失败还有一种更隐晦的情况:脚本依赖的外部环境在 cron 运行时并不完备。举个例子,脚本要往某个数据目录写入文件,但这个目录依赖一块手工挂载的磁盘;你在终端手动执行时磁盘已经挂上了,而服务器开机时这块盘因为 fstab 配置问题没有自动挂载,等到 cron 任务执行时目录根本不存在,脚本自然写不进去。
再有就是 sudo 相关的问题。cron 环境没有 tty,脚本里的 sudo 命令如果配置了需要交互式输密码,就会卡住或者直接失败。如果你确实需要在定时任务里用 sudo,必须配置 NOPASSWD:
# 在 /etc/sudoers.d/ 下新建配置文件 youruser ALL=(ALL) NOPASSWD: /opt/reload_service.sh文件依赖这一块排查起来比较花费时间,因为不会像“命令找不到”那样直接报错。我的建议是:如果怀疑是这类问题,先在脚本入口附近加上调试输出,把当前目录、PATH、挂载列表、用户身份都打个日志,再手动跑一次和定时跑一次对比,差异一眼就能看出来。
3.4 最小化验证法:两分钟内区分“没触发”和“触发了没成功”
环境问题和权限问题排查完,还有一个判断动作很重要:确认任务到底有没有被 crond 触发。很多人在这一步上是靠猜的,其实只需要一个最小化测试任务就能得出确定结论。
先在 crontab 里加一行:
* * * * * echo "[$(date)] cron test ok" >> /tmp/cron_test.log然后等一两分钟,查看 /tmp/cron_test.log 是否出现内容。
这里有一个关键的逻辑分叉:
- 如果这个测试任务正常写入日志,说明 crond 调度链路本身没有问题,你的任务不生效大概率出在脚本层或环境层,继续往脚本内部排查。
- 如果连这个最简单的 echo 都不执行,说明问题出在配置层或调度层,回到第 2 步重新检查语法和服务,查日志时也更容易确认责任方。
这个方法之所以有效,是因为它把“任务没触发”和“任务触发了但脚本没成功”这两类问题清晰地切开。很多时候排查半天都找不到原因,就是因为把这两种情况混在一起看,一会儿怀疑时间,一会儿怀疑脚本,结果哪边都没查透。
4. 第三步核查:时间语义、时区与系统运行状态
配置没错、脚本也没问题、测试任务能跑通,但你的正式任务还是没在预期时间执行。那就要回到时间本身去较真了。这里的“时间”不光是看系统时间准不准,还包括 crontab 对时间的理解是否符合你的预期。
4.1 时区与系统时间:服务器认为的“现在”是否正确
首先确认系统时间:
date timedatectl重点看两件事:一是当前时间是不是你业务预期的时间,二是时区设置是否合理。很多云主机的默认时区是 UTC,如果你在配置任务时按本地时间想了,比如以为设置的是每天凌晨 2 点,实际执行却是北京时间早上 10 点,结果当然对不上。
容器场景更坑。容器的 /etc/localtime 不一定和宿主机一致,如果你在 Docker 容器里跑了 cron 服务,最好在 Dockerfile 里显式设置时区,否则任务执行时间会和宿主机的预期产生偏移。
这类问题还有一种极隐蔽的表现:服务器时间被 NTP 大幅校正。cron 调度器在触发任务时会判断当前时间是否落在触发窗口内,如果 NTP 突然把系统时间从 1:58 拨到 2:01,中间这一段的触发窗口可能被跳过,任务就漏执行了。这种情况在日志里很难看出来,但如果你遇到“偶尔一次没跑,第二天又正常”的诡异现象,就要往这个方向想一想。
4.2 周字段与日字段的 OR 关系:最容易被误解的语法
回到之前埋下的伏笔。crontab 的五个时间字段之间,大部分都是 AND 关系,比如“3 月 15 日 2:30”这样的组合很好理解。但日字段和周字段之间是 OR 关系,这是从 Crontab 诞生就存在的设计,也是无数新手和老手都会掉进去的坑。
举个例子:
30 2 1 * 5你以为它是“每月 1 号且正好是周五才执行”,但实际含义是“每月 1 号执行,同时每周五也执行”。也就是说,这个任务一个月的执行次数可能远大于 1。
官方文档 man 5 crontab 里写得很清楚:当日和周字段都被指定时,任何一个匹配都会触发任务。想要实现“满足多个条件同时成立才执行”,标准 crontab 语法做不到,需要用脚本自行判断:
30 2 1 * * bash -c 'if [ "$(date +\%u)" -eq 5 ]; then bash /opt/backup.sh; fi'这里用了一个小技巧:让任务在每个月的 1 号都触发,但进入脚本后先用 date +%u 判断当前星期几,是周五才真正执行备份逻辑。% 号在 crontab 里做了转义,实际情况中我就是这么绕过 OR 语义的。
4.3 任务重叠、休眠与资源竞争:时间对不代表结果对
还有一种情况,任务在 cron 日志里明明显示执行了,但实际结果不对。这时候要检查脚本的执行时长和任务间隔是否匹配。
设想一个场景:数据同步脚本正常跑完需要 40 分钟,你在 crontab 里配的是每 30 分钟执行一次。第二次触发时上一次的进程还没结束,两个同步进程同时写同一批文件,轻则数据错乱,重则互相锁死。
对这种问题,最直接的手段是给脚本加执行锁:
#!/bin/bash exec 9>/tmp/mytask.lock flock -n 9 || exit 1 # 真正的业务逻辑flock 的原理是把锁绑定在文件描述符上,如果拿不到锁就直接退出,避免重复执行。这也是我在生产环境脚本里必加的一行,成本极低,收益极高。
另外,如果服务器的负载长期处于高位,crond 可能会延迟任务触发,任务实际执行时间和配置时间会有一个肉眼可见的偏移。如果发现任务执行没问题但只是“迟到了”,就去查一下触发时刻的系统负载。
4.4 两分钟验证法:把理论排查变成现场证据
第 3.4 节里我提到的最小化测试任务,在这一步还能再用一次,不过用法不一样。
前面几步排查完,你会对问题原因有一个大概的判断。这时直接在 crontab 里加一个临时任务,把时间设置为两分钟后执行,观察日志确认触发时间是否精确落在你期望的时间点上。
# 假设现在时间是 14:33,那么这样配置 34 14 * * * bash /opt/test_job.sh等你执行到这个临时任务后,去 /var/log/cron 或者脚本自己的日志里看触发时间。这一步能把“我以为的时间”和“系统实际的时间”之间的偏差直接暴露出来。验证完记得把临时任务删掉,我见过不少测试任务忘记清理,结果变成一条永不消失的垃圾任务,后续排查新问题时还会干扰判断。
5. 日志抓取:从系统 cron 日志到脚本执行痕迹的完整链路
排查 crontab 问题,日志是唯一的图钉。前面说的三步核查,每一步几乎都要靠日志来验证。日志抓取也分几个层次,从系统级到脚本级,一层一层往下看。
5.1 系统级日志入口:/var/log/cron 怎么读
CentOS、RHEL 系的系统里,cron 日志默认写在 /var/log/cron,这是最直接的入口。用 tail 或者 grep 过滤出自己关心的内容:
grep 'backup' /var/log/cron tail -f /var/log/cron日志里重点看几类记录。第一类是任务被加载时的记录,类似 CRON (root) CMD (bash /opt/backup.sh),这代表 crond 认为任务到了触发时间并启动执行。第二类是会话启动和结束记录,类似 pam_unix(cron:session): session opened for user root 和 session closed for user root,前者代表任务被拉起,后者代表任务退出。第三类是错误信息,虽然 cron 本身不记录命令内部的报错,但如果有语法问题或权限问题,服务启动时会在日志里留下线索。
Debian、Ubuntu 系的情况稍有不同。很多新装的系统默认不记录单独的 cron 日志,需要手动开启 rsyslog 的 cron 模块。编辑 /etc/rsyslog.d/50-default.conf,找到 cron 相关的行,取消注释,然后重启 rsyslog:
systemctl restart rsyslog之后 /var/log/cron 就会开始滚动记录了。这一步很多人不知道,导致他们在 Debian 系服务器上找不到任何 cron 日志,排查无从谈起。
5.2 journald 里的 crond 运行轨迹
在使用 systemd 的操作系统上,crond 作为 systemd 服务运行,日志也会被 journald 收集。如果你觉得 /var/log/cron 不够细致,或者想确认服务本身的启停时间、重启原因,可以看 journal 视角:
journalctl -u crond journalctl -u cron --since "2 hours ago"journal 里能看到 crond 服务进程的启动信息、配置重载信号,以及部分任务执行记录。配合 -f 参数还能实时跟踪:
journalctl -u crond -f这种方式在容器场景下特别有用,因为很多容器镜像没有配置 rsyslog,/var/log/cron 根本不存在,只有 journald 这个统一的日志出口。
5.3 脚本输出重定向:不要让输出凭空消失
crontab 默认的行为是:如果被执行的命令产生了标准输出或错误输出,crond 会尝试把这些输出通过邮件发送给任务所属用户。但绝大多数服务器上压根没装邮件服务,或者邮件发送失败,于是脚本输出就凭空消失了。这也是很多人“看着日志里触发了,但不知道脚本干了什么”的原因。
正确的做法是强制重定向。给任务加日志输出:
30 2 * * * bash /opt/backup.sh >> /opt/logs/backup.log 2>&1把标准输出和错误输出都写进同一个日志文件,下次排查时打开这个文件,脚本的执行过程和报错内容一目了然。
更规范一点的做法是区分输出流,1 表示标准输出,2 表示错误输出:
30 2 * * * bash /opt/backup.sh >> /opt/logs/backup_out.log 2>> /opt/logs/backup_err.log错误日志单独存放的好处是,任务执行完检查脚本时,只用看错误日志就能快速定位异常,不用在一大坨正常输出里翻找。
5.4 主动探针:在脚本里埋点确认执行痕迹
如果日志重定向已经做了,但脚本日志文件里一片空白,那说明脚本根本没跑起来,或者跑了但连写日志的权限都没有。这时候需要在脚本入口和出口各自埋一个“探针”:
#!/bin/bash echo "[$(date)] ===== task start =====" >> /opt/logs/task_trace.log # 业务逻辑 echo "[$(date)] ===== task end =====" >> /opt/logs/task_trace.log这样做的好处是:就算业务逻辑挂在中途,你也能从探针日志里看到 start 记录而没有 end 记录,从而确定脚本确实被拉起,且问题发生在业务逻辑段。如果连 start 都没有,说明任务根本没有触发,回到调度层去查。
埋探针这个方法看似笨,但在实际定位问题时比任何日志分析工具都快。我可以说一个数据:我经手的 crontab 排障中,有一半的问题靠“入口探针 + 输出重定向”就能在十分钟内定位,根本不需要动 GDB 或者 strace 那些重型工具。
6. 五个高频事故复盘:从现象到定位再到修复
最后用几个我亲手处理过的典型案例来收个尾。每个案例都按“现象、排查、修复”的结构来写,相当于把上面的三步核查和日志抓取再完整走一遍。
6.1 案例一:命令“找不到”背后的 PATH 问题
现象:某数据分析团队搭了一个每日统计任务,crontab 里写的命令是 python3 /home/data/src/report.py。手动执行一切正常,但 cron 执行后没有任何输出,日志文件是空的。
排查:先确认 crond 服务正常,再看 /var/log/cron 确认任务确实触发了。因为脚本输出被重定向到了日志文件,打开日志发现报的是 python3: command not found。
修复:在脚本开头加上 export PATH="/usr/bin:/bin:/usr/local/bin:/usr/local/sbin:$PATH"。之后任务恢复正常。
教训:手动执行和 cron 执行的第一个差异就是 PATH。任何需要交给 cron 的脚本,优先显式设置 PATH,别偷懒。
6.2 案例二:% 号截断命令,任务只跑了一半
现象:某同事的清理任务每天执行,但清理效果时好时坏,后来发现任务日志里只执行了前半段,后半段命令直接消失。
排查:看 crontab 配置,发现写的是类似 echo "backup_$(date +%Y%m%d)" 的命令。问题就出在 % 上,cron 把 % 当成了换行符,命令在 % 处被截断,后面的参数被丢给了前一个命令做标准输入。
修复:把 % 转义成 %:
30 2 * * * echo "$(date +\%Y\%m\%d) backup done" >> /opt/logs/clean.log 2>&1教训:我现在的习惯是,凡是 crontab 配置里需要日期格式之类的参数,全部在脚本内部用变量生成,crontab 行里只写脚本调用,彻底避开 % 问题。
6.3 案例三:重启后任务消失,配置去哪了
现象:某台服务器重启后,所有用户级定时任务全部失效。crontab -l 显示为空。
排查:查看 /var/spool/cron/ 目录,文件还在;但 crontab -l 却读不到内容。后来发现是 /var/spool/cron/ 所在分区权限异常,或者有人在初始化脚本里执行了 crontab -r。还有一种更常见的情况:误操作把任务写到了另一个用户下,自己查看 root 的 crontab 自然什么都看不到。
修复:检查正确用户的 crontab;如果文件确实丢失,从备份恢复。经过这次事件后,我给定时任务定了两条规则:一是定期备份 crontab 配置,二是任何服务器重启后都要跑一遍检查脚本,确认核心任务全部在线。
crontab -l > /path/backup/crontab_$(date +%F).txt6.4 案例四:时区偏差,任务总是早来一小时
现象:业务团队反馈数据同步任务每天都会提前一小时执行,早上 6 点的任务 5 点就跑完了。
排查:date 命令一看,系统的时区是 UTC,而业务预期的是本地时间。cron 调度完全按照系统时区来,所以任务比预期早了一个小时。
修复:根据业务实际时区调整系统时区设置。这种事情听起来简单,但在多区域部署的服务器集群里特别常见,因为镜像模板是统一的,往往在初始化时没把时区配置好。
另外一个建议是,在脚本内部尽量用 UTC 或者用标准的偏移量输出时间,避免脚本逻辑里编写对具体时区的假设。
6.5 案例五:脚本权限不足,日志里干干净净
现象:某数据采集脚本每 10 分钟执行一次,但连续一周数据都没更新。奇怪的是 /var/log/cron 里有触发记录,脚本日志也有 start 探针,但真正写数据的步骤一条记录都没有。
排查:用运行 cron 的用户身份检查脚本的写入目标目录,发现采集的数据目录所有者是 root,而 cron 任务运行在普通用户下,权限只有 r-x。脚本尝试创建新文件时被拒绝,但由于脚本里的重定向和错误处理不够严格,失败的输出没有进日志。
修复:把数据目录的属主改为运行用户,或者调整 cron 任务到有权限的用户下。然后再加一层保险,在脚本里把写文件失败时的错误信息明确重定向到错误日志:
if ! touch "$outfile"; then echo "[$(date)] write failed: $outfile" >> /opt/logs/error.log exit 1 fi教训:日志里“没有内容”有时也是一种内容。它说明脚本早期的步骤已经失败,但所有报错都被吞掉了。这也是我在所有脚本里坚持加输出重定向和入口探针的原因,因为绝大多数任务不会主动告诉你它失败了。
这套三步核查和日志抓取的方法,我在实际的服务器维护中反复使用过,也分享给了团队里的同事。如果你正在被某个定时任务折磨得头疼,不用把文档翻个遍,先照着这个流程走一遍,大概率能定位到问题。最后再分享一个小习惯:任何新加的生产级定时任务,我都会先做三件事——脚本开头显式声明 PATH、crontab 行加输出重定向、入口埋探针日志。这三个动作花不了两分钟,但能让你以后排查时节省两个小时。