杂记 › 技术笔记 › Crontab表达式看不懂?在线解析并预测下次执行时间

Crontab表达式看不懂?在线解析并预测下次执行时间

年底接手一台离职同事留下的服务器,运营抱怨"月初的对账邮件一周收到三封"。我去翻 crontab,只有一行 0 9 1 * 1,脑子里的第一反应是"每月 1 号、并且正好是周一的那个 9 点"。直到把执行日志按时间排开才发现不对:它在每月 1 号跑,每个周一也照样跑——cron 里"日"和"周"两个字段同时被限定时是或的关系。一次语义误读,就变成了每周一次的重复推送。这件事逼着我把 cron 的语法重新过了一遍,下面是真正会踩到的部分。

一、五个字段就是"分 时 日 月 周",边界比想象的小

读法是从左到右,一个空格分隔一个字段,顺序固定的:分钟、小时、日、月、星期。很多人把"日"和"星期"的优先级想复杂了,其实它们只是两个独立的筛选条件。

字段取值范围写 * 的意思容易看错的地方
分钟0–59每一分钟没有 60,60 * * * * 是非法的
小时0–23每一小时0 是午夜 0 点,不是"没有"
日1–31每天起始是 1,不是 0
月1–12每月不是 0–11,和某些编程语言不一样
星期0–7每周每天0 和 7 都表示周日,1 是周一

所以 0 2 * * * 是"每天 02:00",30 6 * * 1-5 是"工作日 06:30"。读表达式时我习惯先盯最后两个字段:只要"日"和"周"都不是 *,就要按"或"来理解,这是最容易和直觉冲突的一条。

二、四个符号,*/5 的含义最容易想歪

  • * 任意值;, 列举,如 0,15,30,45 是这个小时内固定的四个时刻。
  • - 范围,如 9-18 是 9 到 18 这两个端点都包含的闭区间。
  • /n 步长。关键点:cron 不是"上次跑完等 n 分钟再跑",而是在固定的时间网格上取整除的值。*/5 * * * * 落在每小时的第 0、5、10……55 分,整点必触发;*/7 * * * * 是 0、7、14、21、28、35、42、49、56 分,到下一小时又从 0 开始,所以相邻两次的间隔在 4 分钟和 7 分钟之间跳,而不是恒定 7 分钟。这就是"每 7 分钟"这种需求永远写不准的原因。
  • 步长还能带起点:5-59/10 是第 5、15、25、35、45、55 分,用来把任务错开整点,避开大家挤在 :00 跑的时段。

三、我确认一个表达式是否写对的流程

  1. 先在纸上把需求写成中文句子,明确到"哪一天的几点几分""一周触发几次""一个月触发几次",给出具体次数。
  2. 再翻译成表达式,然后把它丢进Crontab 解析里,看每个字段被解释成什么含义,重点确认"日"和"周"是不是自己想要的组合。
  3. 看它预测的未来 10 次执行时间点,把星期和时刻逐个念一遍:我要求"每周三 03:30",预测里就必须全是周三,间隔必须是 7 天而不是 6 或 8 天。
  4. 最后 crontab -l 核对落盘的那一行,注意行首不要有 set -e 之类被当成任务行的内容,也不要有多余的 Tab。

预测执行时间这一步省不掉。日志出问题时,"到底几点跑的"是唯一有效的证据,而表达式本身不会告诉你这个。

四、另外四个把人卡住的细节

  • 时区:cron 按服务器本地时区解释每个字段。一台 UTC 机器上的 0 3 * * *,对应的是北京时间 11:00,正好撞在业务高峰上。核对办法是比对 date +%s 和 date 的输出,用时间戳转换把同一个数字分别按 UTC 和本地时区展开,8 小时的偏差一眼就能看出来;较新的实现(如 cronie)还支持在文件顶部写 CRON_TZ 或 TZ 单独指定某条任务的时区,但这类变量并非所有 cron 版本都认,用之前先在测试机上确认一次。
  • 百分号:% 在 crontab 里表示"换行并把后续内容当标准输入",所以 date +%Y%m%d 必须写成 date +\%Y\%m\%d,否则任务会静默失败。
  • 环境变量:cron 给的 PATH 很窄,通常只有 /usr/bin:/bin,脚本里直接调 node、python3、aws 经常报"command not found",而手工执行完全正常。稳妥做法是脚本第一行显式加载环境,或全部用绝对路径。
  • 没人收的输出:任务有输出时 cron 会尝试发邮件,多数服务器没有 MTA,看起来就像"什么也没发生"。加一句 >> /var/log/cron_backup.log 2>&1,再把 MAILTO="" 关掉,至少留下时间戳线索。

五、常抄的表达式与它们的真实触发次数

表达式含义一天触发次数
* * * * *每分钟1440
*/15 * * * *每 15 分钟(:00 :15 :30 :45)96
5-59/10 9-18 * * *9 到 18 点内每小时的第 5、15、25、35、45、55 分60
30 3 * * 0每周日凌晨 3:30平均 0.14
0 2 1 * *每月 1 日 02:00平均 0.033
0 9 * * 1-5工作日 09:00平均 0.71
0 9 1 * 1每月 1 日 09:00 或 每周一 09:00约 0.17(一年约 62 次)

最后一行就是我踩的那个。真要"每月第一个周一",cron 的字段表达不出来,得让脚本自己在被触发时判断 [ $(date +%d) -le 7 ],条件不满足就直接退出。

六、常见疑问

Q:cron 能按秒执行吗?

标准 crontab 是 5 位,最小粒度就是 1 分钟,做不到秒级。需要更高频率通常用 * * * * * 里跑一个内部循环,或改用带 6/7 位(含秒、年)字段的调度框架,那类语法和 cron 并不完全通用。

Q:周日到底写 0 还是 7?

两个都可以,0 和 7 都指周日。团队里最好统一一个,混着写会让 1-7 和 0-6 这种区间变得难以判断。

Q:改完 crontab 要重启服务吗?

用 crontab -e 编辑的话不用,保存后守护进程会自动重新加载;直接编辑 /etc/crontab 或 /etc/cron.d/ 下的文件时,注意文件属主和权限,权限过宽会被忽略。

Q:为什么脚本手动跑没问题,放进 cron 就失败?

九成是环境差异:PATH 太短、HOME 没设、相对路径以 $HOME 为基准、当前工作目录不是脚本目录、或者非交互 shell 没有加载你的 .bashrc。排查时在任务里先执行一次 env > /tmp/cron_env.txt,和手动跑的 env 对比,差异会直接暴露出来。

Q:任务卡住没跑完,下一次还会触发吗?

会。cron 不关心上一次是否结束,同一任务可能重叠执行。耗时不确定的备份要自己加锁,常见做法是用 flock 包住命令,拿不到锁就直接跳过。

回头看那三封重复的对账邮件,问题不在服务器上,而在我把"和"读成了"与"。表达式这种东西,写出来只是一半,另一半是确认它到底会落在哪几个时刻——需求先写成一句中文,再用未来执行时间点去对,比翻文档管用。

📎 相关文章

PNG 压了三遍只减 426 字节:无损压缩的真实边界 2026-10-22 WebP 转 PNG 后体积涨了几倍:什么时候必须转,怎么把涨幅压住 2026-10-22 截图缩到四分之一,小字糊成一团的三种原因 2026-10-22 同样裁 300×300,头像为什么总偏右下角半格 2026-10-22 按每月 1.7 倍预估 18 个月,脚本和表格为什么给出两个数 2026-10-22