Linux crontab 定时任务的五个常见坑
在 Linux 运维和 CI/CD 中,crontab 是最常用的定时任务工具之一。它语法简洁、随系统启动,却常常在最不引人注目的细节上让人掉坑。下面列出我在日常踩过的五个常见坑,以及对应的避坑方法。
一、环境变量"凭空消失"
很多人把交互式 shell 里跑得好好的脚本丢进 crontab,结果一跑就报错:命令找不到、变量为空。原因是 cron 启动任务时使用的是一种"最小化环境",PATH 通常只有 /usr/bin:/bin,且不会加载 /etc/profile 和 ~/.bashrc。USER 之外的用户变量一概不继承。
做法:在脚本开头写明解释器与环境,例如 #!/bin/bash,并在 cron 行或脚本内显式 export PATH=/usr/local/bin:/usr/bin:/bin,或者把脚本中所有命令都写成绝对路径。最稳妥的方式是把 cron 的 stdout/stderr 重定向到日志文件:* * * * * /path/to/script.sh >> /var/log/mycron.log 2>&1,这样出问题时一目了然。
二、时间字段误解:* * * * * 不是"每分钟执行一次就完事"
cron 的五个时间字段分别是 分(0–59)、时(0–23)、日(1–31)、月(1–12)、星期(0–7,0 和 7 都是周日)。两个容易被忽略的细节:
- 日和星期同时被指定(都非
*)时,多数 Vixie-cron 实现采用**“取并集”**策略,即满足任意一个就会触发,而不是直觉上以为的"取交集"。 - 0 和 7 都表示周日,
* * * * 0,7并不是每周日跑两次,但写出来会让人以为会执行两次。
做法:只设"日"或"星期"其中之一,另一个写 *。需要"每月最后一个工作日"这类复杂逻辑时,别硬套 cron 表达式,改用脚本内用 date/cal 判断,或交给 systemd timer。
三、脚本里的相对路径
跟环境变量相关但经常被当成独立问题:脚本中 cd data/、cat config/app.yml 这种相对路径在 cron 下通常以根目录 / 或用户 home 为工作目录启动,导致文件"不存在"。
做法:脚本开头固定工作目录:
#!/bin/bash
cd /opt/myapp || exit 1
source /opt/myapp/venv/bin/activate
python run.py
或者直接用绝对路径调用,不要依赖当前目录。
四、权限和属主:crontab -e vs /etc/crontab
crontab -e 编辑的是当前用户的 crontab,行里不需要写用户名;而 /etc/crontab 和 /etc/cron.d/ 下的文件每行必须包含用户名字段(共 6 个时间+用户+命令)。把用户 crontab 的写法直接复制到 /etc/cron.d/myjob,cron 会把你的"命令"当用户名、把真正的命令当事件字段,任务要么不执行要么报语法错。
另外,cron 对可执行文件有权限要求:crond 本身和 cron 可执行文件需属于 root 且没有写权限(否则 cron 出于安全考虑直接退出)。如果因为 chown/chmod 改乱了 /usr/sbin/cron 的权限,系统级的定时任务会集体罢工。
五、并发与重叠执行
cron 只看调度时刻,不管上次有没有跑完。脚本跑 50 分钟而调度是每分钟一次,就会叠加出几十份副本争抢锁、写同一个文件,最终数据混乱。
做法:
- 用
flock互斥:* * * * * /usr/bin/flock -n /tmp/myjob.lock /path/to/script.sh - 或在脚本首行加
exec 200>/tmp/myjob.pid; flock -n 200 || exit 1 - 对于不需要高频轮询的场景,改用
@hourly、@daily或 systemd timer 的AccuracySec/RandomizedDelaySec。
小结
crontab 的"简单"恰恰是它的陷阱所在:环境不继承、相对路径失效、日/星期是并集、用户/系统级语法不同、没有内置去重。养成三个习惯——绝对路径 + 显式 PATH + 重定向日志,基本能避开 90% 的线上"定时任务不执行"排查。遇到更复杂的调度依赖(失败重试、幂等、上下游),尽早交给 systemd timer、Airflow 或云平台的定时任务服务。
参考:
man 5 crontab、Vixie-cron 源码对日/星期字段的注释、flock(1)。
- 点赞
- 收藏
- 关注作者
评论(0)