Cron 定时任务排错:为什么你的脚本手动能跑、定时却失败
几乎每个运维人都遇到过这个场面:写好的脚本在终端里跑得好好的,一放进 crontab 就悄无声息地失败,日志里什么都没有。问题很少出在脚本本身,大多出在 cron 的运行环境和手工执行时的环境不一样。把这几个坑排一遍,多数“定时失败”都能定位。
一、环境变量不一样:PATH 是头号嫌疑人
手工执行时,你的 shell 加载了 .bashrc、.profile,PATH 里有 /usr/local/bin、/opt/xxx/bin 这些自定义路径。cron 启动的任务只有一个极简环境,PATH 通常只有 /usr/bin:/bin。你脚本里写的 docker、python3、node 这些命令,如果装在 /usr/local/bin 下,手工能跑,cron 里就是 command not found。
排查办法很直接:在脚本开头加一行 env > /tmp/cron_env.txt,对比手工环境的 env,缺什么补什么。最稳妥的写法是脚本里用绝对路径,或者在脚本第一行显式 export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin。不要依赖 cron 的默认 PATH。
二、相对路径在 cron 里没有意义
脚本里写 cd /data/backup 或者 ./config.json,手工执行时你在家目录,路径是对的;cron 执行时的工作目录通常是用户的家目录,但这并不可靠,不同系统、不同 cron 实现可能不一样。凡是脚本里出现的相对路径,定时跑都可能指向错误的位置。
规则很简单:定时任务里用到的所有路径都写绝对路径。脚本自己所在的位置也别用相对路径取,用脚本所在目录的绝对路径拼出来:DIR=(dirname “$0”)" && pwd)。这样不管从哪里触发,脚本找到的都是同一份文件。
三、输出没地方去,等于没有日志
cron 任务的 stdout 和 stderr 默认会通过邮件发给任务所属用户,而大多数服务器根本没配本地邮件,输出直接丢了。于是任务失败了你也看不到任何报错,这是最常见的“悄无声息”。
正确的做法是每条 cron 都显式重定向输出:
0 3 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
日志文件要记得配 logrotate,不然几个月后它自己就把磁盘写满了。调试阶段也可以先把输出重定向到一个临时文件,确认跑通了再收进正式日志。
四、权限和用户身份
crontab -e 编辑的是当前用户的定时任务,root 的任务和普通用户的任务是两回事。脚本手工用 root 跑没问题,放进普通用户的 crontab 里就可能因为读不到某个目录、写不进某个日志而失败。反过来,普通用户能跑的脚本,root 的 cron 跑起来也可能因为环境变量不同而出问题。
排错时先确认两件事:这条 cron 属于哪个用户(crontab -l -u 用户名),以及该用户手工执行同一条命令是否成功。权限问题不要用 chmod 777 粗暴解决,改成任务所属用户应有的最小权限。
五、时间表达式写错了自己还不知道
每月 1 号跑一次写成 0 0 1 * * 是对的,写成 0 0 * * 1 就变成每周一跑了。星期和日期同时写时,cron 是“或”的关系:0 0 1 * 1 表示每月 1 号或者每周一都会跑,不是“每月 1 号且是周一”才跑。这个细节坑过不少人。
改完 crontab 后,用 crontab -l 逐行核对一遍。如果系统装了 systemd,也可以考虑用 systemd timer 代替 cron:它有明确的日志(journalctl -u xxx.timer)、失败重试和依赖管理,排错比 cron 友好得多。
六、一个可复用的排错顺序
遇到定时任务失败,按这个顺序查,基本不会走弯路:先看 cron 服务本身是不是在运行(systemctl status cron);再看 /var/log/cron 或 /var/log/syslog 里这条任务有没有被触发;然后看脚本的输出日志;最后在脚本里加 set -x 或者 env 输出,对比手工环境和 cron 环境的差异。
多数情况下,问题就落在 PATH、相对路径、输出丢失这三处。把脚本写成“在任何环境下都能跑”的样子,定时任务自然就稳了。
小结
Cron 定时任务失败,十有八九不是脚本写错了,而是它跑在一个和你手工执行时完全不同的环境里。用绝对路径、显式设置 PATH、重定向全部输出、确认任务所属用户,这四条做到了,定时任务的排错成本会大幅下降。
(本文为个人运维实践记录。)
- 点赞
- 收藏
- 关注作者
评论(0)