Cron 表达式解析 — 这行定时任务到底说什么、下一次什么时候跑

粘一行 crontab,它按 5 段或 6 段拆开,列出每一段真正匹配的所有取值,讲清“日期与星期是或的关系”,再用这台机器的日历算出接下来几次实际运行时刻。拆解全在这台机器上完成,你输入的内容不会离开这台设备。

常用写法 — 点一下直接载入

A · 逐段拆解

B · 接下来会执行的时刻

五段的顺序,以及日期与星期的或关系

五段的顺序,就是 cron 读的顺序

分钟  小时  日  月份  星期
0-59  0-23  1-31  1-12  0-7   (0 和 7 都是周日)

每一段可以写单个数字、列表(1,15,30)、范围(1-5)、步长(*/10、0-59/5),月份和星期两段还认三个字母的名称 JAN…DEC、SUN…SAT。除了这些,一行 crontab 里剩下的就只有别名(@daily、@weekly、@hourly、@reboot)和笔误。

6 段是前面多一个秒,7 段是最后多一个年。这两种在 Vixie cron 和 Linux /etc/crontab 里都不存在,属于 Quartz / Spring @Scheduled / EventBridge 方言。三种都读,并且会明确说明按的是哪一种。

最容易踩的一条:日 与 星期 是"或"

两段日期列不是按直觉合并的。当两段都做了限制,cron 在任一段匹配时就执行,所以 0 0 13 * 5 是"每月 13 号,外加每个周五",而不是"恰好落在周五的 13 号"。只要其中一段是 *(或 ?),就只由另一段决定 —— 这也正是"周五 13 号"这种需求写不进一行 crontab 的原因,常见做法是在任务体里自己加判断。

? 的意思是"不特指",只在那两段合法,它是 Quartz 表达 * 同一意图的写法;两者被同等对待,但解释里仍保留 ?,因为要求 6 段的调度器会拒绝日列里的 *,反过来也一样。

“接下来 8 次"是对着什么算的

那张表是把日历一格一格往前推出来的 —— 先月、再日、再时、再分、再秒,用的是浏览器自带的 Date,因此:

  • 由你这台机器的时区决定。 服务器上的 cron 用它自己配置里的时区(Linux 上可以 CRON_TZ=,否则就是系统时钟)。表格下面那行会写清本次用的是哪个时区。
  • 夏令时跳掉的那一小时不会凭空补出来。 时钟从 01:59 直接跳到 03:00 的那天,30 2 * * * 没有匹配的时刻,结果就顺延报下一天。你的服务器到底是跳过、延后跑一次,还是秋天那一小时跑两次,是实现对齐问题,权威在 man 5 crontab,不在一个网页。
  • 短月份直接不匹配。 0 9 30 * * 完全跳过 2 月,0 0 29 2 * 只在闰年出现,所以它的"下一次"往往要等上好几年。
  • 一年内的次数是设计成近似的。 那一格统计接下来 366 天里命中的次数,数到 2000 就停并显示 2000+;每分钟跑一行的表达式当然早就超了。

厂商扩展:宁可拒绝,也不猜

L(当月最后一天)、W(最邻近的工作日)、#(第几个周几,如 5#3)、倒着数的范围(FRI-MON)、步长 0 —— 这些在某些调度器里存在,但各家语义并不一致。所以不发明含义,只把读不懂的那个 token 和它来自哪一段报出来 —— 这也正是你的 crontab 该有的行为:cron 常常是整行静默作废,只在日志里留一条难看记录。

常见问题

Q:0 0 * * 0-6 为什么说"每天”? 因为 0–6 就是整周,而"日"那段是 *,两段说的是同一件事。man 5 crontab 认为写全的星期段属于"有限制",于是 0 0 13 * 0-6 会在 13 号以及每一天都触发 —— 效果就是每天。

Q:小时段的 */5 是每五小时吗? 是 —— 从 00 起,所以 00:00、05:00、10:00、15:00、20:00。它不是“距上次运行五小时”:cron 不记上一次,只把当前时钟和各段比对。

Q:说明明写还有 3 小时,我的服务器已经跑过了。 最常见是时区差:服务器那份时钟跟你不一样。其次是日期两段里 */? 写反。再次是时分段都没错、但命令本身失败,那没有任何解析器能看出来。

Q:表达式会被传到别处吗? 不会。解析就是脚本里的字符串切分,时刻来自浏览器自己的日历,没有哪一步要经过服务器,你输入的内容不会离开这台设备。

微信
微信
支付宝
支付宝