cron 表达式怎么写(含常见写法速查)

标准 crontab 的表达式就是 5 个用空格分开的字段:分 时 日 月 周。顺序从小到大,且日和周是并列关系而不是同时满足关系——这一条坑了很多人,后面单独说。

五个字段的取值范围

位置 字段 范围 备注
1 分钟 0–59
2 小时 0–23 24 小时制
3 日(月内) 1–31
4 月 1–12 或 JAN–DEC
5 周(星期) 0–7 0 和 7 都表示周日

四个特殊字符

字符 含义 例子
* 每个单位都匹配 * * * * * 每分钟
, 枚举多个值 0 6,18 * * * 每天 6 点和 18 点
- 连续区间 30 2 * * 1-5 周一到周五 02:30
/ 步长 */10 * * * * 每 10 分钟

步长可以写在区间上:0 9-18/2 * * 1-5 表示工作日 9 点到 18 点之间每 2 小时(9、11、13、15、17 点)。

速查表

表达式 含义
* * * * * 每分钟
*/5 * * * * 每 5 分钟
0 * * * * 每小时整点
15 * * * * 每小时的第 15 分钟
0 3 * * * 每天 03:00
30 2 * * 1-5 周一至周五 02:30
0,30 9-18 * * 1-5 工作日 9–18 点每半小时
0 0 * * 0 每周日 00:00
0 0 1 * * 每月 1 号 00:00
0 0 1 1 * 每年 1 月 1 日 00:00
0 4 1 * * 每月 1 号 04:00

还有一组简写,等价于固定表达式:

简写 等价写法
@hourly 0 * * * *
@daily / @midnight 0 0 * * *
@weekly 0 0 * * 0
@monthly 0 0 1 * *
@yearly / @annually 0 0 1 1 *
@reboot 系统启动时执行一次(没有等价表达式)

日和周是 OR 关系

0 0 1 * 5 在标准 Vixie cron 里的含义是"每月 1 号 或者 每周五的 00:00",不是"每月 1 号且这天是周五"。只要这两个条件任意成立就会触发。

想表达"每月 1 号,且必须是周五"这类同时满足条件,标准 cron 写不出来,只能在脚本里判断:

0 0 1 * * [ "$(date +\%u)" = "5" ] && /usr/local/bin/report.sh

注意 crontab 文件里 % 有特殊含义(表示换行,其后的内容会作为命令的标准输入),所以必须写成 \% 转义。

5 段、6 段、7 段的区别

系统 段数 顺序
标准 crontab(Vixie / BusyBox) 5 分 时 日 月 周
Quartz(Java) 6 或 7 秒 分 时 日 月 周 [年]
Spring @Scheduled 6 秒 分 时 日 月 周
Kubernetes CronJob 5 分 时 日 月 周

Quartz 多出来的第一段是秒,而且它额外支持 ?(不指定,用于和"日"互斥)、L(最后一天 / 最后一个星期几)、W(最近的工作日)、#(第几个星期几,如 6#3 表示第三个星期五)。这些字符不是标准 cron 的一部分,把 Quartz 表达式直接贴进 crontab 会报错,反之亦然。

真实环境里的四个坑

一、时区。 cron 默认按系统本地时间解释表达式。服务器在 UTC 而你在东八区,写 0 9 * * * 会在北京时间 17 点执行。Debian/Ubuntu 的 cron 支持在 crontab 里写 CRON_TZ=Asia/Shanghai 覆盖,但不是所有实现都支持;较新版本的 Kubernetes CronJob 也提供了 spec.timeZone 字段。最稳妥的做法是统一服务器时区为 UTC,然后按 UTC 写表达式。

二、任务会重叠。 cron 不关心上一次跑完没有。备份脚本跑 10 分钟而间隔是 5 分钟,就会叠起来。用 flock 加一把非阻塞锁:

*/5 * * * * flock -n /tmp/backup.lock /usr/local/bin/backup.sh

三、环境变量几乎不存在。 cron 给任务的 PATH 通常只有 /usr/bin:/bin,你在 ~/.bashrc 里 export 的东西一律读不到。脚本里要么写绝对路径,要么在 crontab 顶部显式声明 PATH=/usr/local/bin:/usr/bin:/bin。

四、输出会被发邮件。 cron 默认把任务的 stdout 和 stderr 收集起来发邮件给 crontab 所属用户。没有配 MTA 时这些输出就丢了,日志莫名其妙查不到。显式重定向:

0 3 * * * /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1

另外提醒一句:cron 的最小粒度是分钟,写不出"每 30 秒"。需要秒级调度要用 systemd timer,或者在脚本里循环加 sleep。

改完怎么验证

crontab 只在加载文件时做语法检查,字段写错通常不报错,只是永远不触发。三步自查:

  1. 确认条目已加载:crontab -l 看内容,systemctl status cron(RHEL 系是 crond)确认守护进程在跑。
  2. 确认它触发过:Debian/Ubuntu 看 grep CRON /var/log/syslog,RHEL 系看 /var/log/cron。这里只能看到"有没有触发",脚本自己的输出取决于你的重定向。
  3. 先用最小间隔试跑:临时改成 * * * * *,等一两分钟确认脚本逻辑正确,再改回真实周期。

"cron 不执行"的排查链,按命中率排序:脚本没有 chmod +x → 脚本内命令用了相对路径 → 依赖的环境变量不存在 → % 没转义 → 表达式字段顺序写错。前三条占了绝大多数。

自己动手试试

相关工具