什么是 cron 表达式?语法、字段与 15 个示例

cron 表达式是一段简短的字符串,用来告诉调度程序何时运行某个任务。标准格式由五个以空格分隔的字段组成:分钟、小时、日期(月中的第几天)、月份和星期。例如,*/15 * * * * 表示“每 15 分钟一次”:分钟字段中的 */15 匹配第 0、15、30 和 45 分钟,其余四个星号表示任意小时、任意日期、任意月份和任意星期。

免费试用:Crontab 生成器 免费使用,无需注册账号。

这个名字来自 Unix 的任务调度程序 cron,但如今同样的语法也用于 CI 流水线、云端调度服务和应用框架,有时还会增加额外的字段。本文介绍 cron 语法、15 个经过核对的示例、常见变体,以及会让任务在错误时间运行的常见错误。阅读时如果想检查某个表达式,可以把它粘贴到免费的 Crontab 生成器中,它会用通俗的语言描述执行计划。

cron 表达式的五个字段

字段从左到右依次为:

位置 字段 允许的值 名称
1 分钟 0–59 –
2 小时 0–23 –
3 日期 1–31 –
4 月份 1–12 jan–dec
5 星期 0–7(0 和 7 都表示星期日) sun–sat

小时采用 24 小时制,下午 2 点写作 14。名称取英文月份或星期的前三个字母,不区分大小写。有些实现(包括 macOS 自带的 cron)不允许在范围或列表中使用名称,因此 1-5 比 mon-fri 的兼容性更好。

在 crontab 文件中,命令写在五个字段之后:0 2 * * * /home/me/backup.sh。Linux 上系统级的 /etc/crontab 还会在命令前多一个用户名字段。

特殊字符:星号、逗号、连字符和斜杠

几乎所有写法都靠这四个字符完成:

字符 含义 示例 匹配
* 任意值 小时字段中的 * 第 0、1、2 … 23 小时
, 列表 小时字段中的 8,20 第 8 和第 20 小时
- 范围(含两端) 星期字段中的 1-5 星期一到星期五
/ 步长 分钟字段中的 */15 第 0、15、30、45 分钟

它们可以组合使用。小时字段中的 9-17/2 匹配 9、11、13、15 和 17 点,0-4,8-12 则是由两个范围组成的列表。

步长在每个周期都会重新开始。分钟字段中的 */7 匹配 0、7、14 … 56,到下一个小时开始时又从 0 算起,所以 :56 到 :00 之间只隔 4 分钟,而不是 7 分钟。如果步长不能整除 60 分钟或 24 小时,间隔就永远不会完全均匀。

15 个 cron 表达式示例

每个示例都逐个字段核对过。时间以调度程序所在的时区为准。

cron 表达式 运行时间
* * * * * 每分钟
*/5 * * * * 每 5 分钟(:00、:05、:10 …)
30 * * * * 每小时的第 30 分钟(00:30、01:30 …)
0 */2 * * * 每 2 小时整点(00:00、02:00 … 22:00)
0 2 * * * 每天 02:00
0 8,20 * * * 每天两次,08:00 和 20:00
0 9 * * 1-5 周一至周五 09:00
*/15 9-17 * * 1-5 周一至周五 09:00 到 17:45 之间每 15 分钟
0 6 * * 0,6 周六和周日 06:00
0 0 * * 0 每周日午夜
0 22 * * 5 每周五 22:00
0 0 1 * * 每月 1 日午夜
0 12 15 * * 每月 15 日 12:00
0 0 1 1,4,7,10 * 每季度:1 月、4 月、7 月和 10 月的 1 日午夜
0 0 1 1 * 每年一次,1 月 1 日午夜

有两个细节很容易被忽略。第一,范围包含两端,所以 9-17 包括 17 点:*/15 9-17 * * 1-5 每天运行 36 次,最后一次在 17:45,而不是 17:00。第二,0 0 1 */3 * 得到的是同样的季度计划,因为月份字段中的 */3 从 1 开始,匹配 1、4、7 和 10 月。

非标准宏:@daily、@weekly 和 @reboot

很多 cron 实现都接受用一个关键字代替五个字段:

宏 等同于 运行时间
@yearly 或 @annually 0 0 1 1 * 1 月 1 日午夜
@monthly 0 0 1 * * 每月 1 日午夜
@weekly 0 0 * * 0 每周日午夜
@daily 或 @midnight 0 0 * * * 每天午夜
@hourly 0 * * * * 每小时的第 0 分钟
@reboot – cron 守护进程启动时运行一次

Vixie cron、cronie 和 macOS 上的 cron 支持以上全部宏;Kubernetes 的 CronJob 支持基于时间的宏,但不支持 @reboot。这些宏不属于 POSIX 标准,在其他环境中使用前请先查看文档。

cron 的变体:秒、年份和额外字符

并不是所有调度程序都只读取五个字段,所以请先确认你要写的是哪种“方言”。

日期与星期字段的陷阱

在标准(Vixie)cron 中,如果日期和星期两个字段都有限定值,二者是“或”的关系,而不是“且”。只要其中一个是 *,就只看另一个。因此,0 9 1-7 * 1 并不表示“每月第一个星期一”。它会在每月 1 日到 7 日的每一天 09:00 运行,另外每个星期一也会运行,合计每月 10 或 11 次。

如果只想在第一个星期一运行,可以在表达式里只保留一个日期字段,另一个条件放到命令中判断:

0 9 1-7 * * [ "$(date +\%u)" = 1 ] && /path/to/report.sh

date +%u 会把星期输出为 1 到 7 的数字,星期一为 1。百分号必须转义为 \%,因为 cron 会把命令中未转义的 % 当作换行符。Quartz 和 AWS 要求在两个日期相关字段之一使用 ?,从而避免了这种歧义。

时区与夏令时

cron 按照负责解析表达式的机器或服务所在的时区运行,而服务器和云平台上通常是 UTC,并不是你的本地时间。在 UTC 服务器上,0 9 * * * 会在 UTC 09:00 运行,也就是北京时间 17:00(UTC+8)。时区转换器 可以帮你把本地时间换算成调度程序所在的时区。

在实行夏令时的时区,还会出现两种特殊情况:时钟拨快时,有一个本地小时并不存在;时钟拨慢时,有一个小时会出现两次。Debian 的 cron 和 cronie 会对固定时间的任务处理这类小幅度的时钟变化:落在被跳过的那一小时内的任务,会在切换后不久补运行;落在重复那一小时内的任务不会再运行一次。其他调度程序的处理方式可能不同,因此最稳妥的做法是让服务器使用 UTC,或者避免把重要任务安排在调整时钟的时段,通常是当地时间 01:00 到 03:00 之间。

如何测试 cron 表达式

写错的 cron 表达式很少会报错,任务只会在错误的时间运行,或者根本不运行。下面这个简短流程可以发现大多数问题:

  1. 用文字复述一遍。 把表达式粘贴到能描述计划的工具里,检查描述是否符合你的本意,尤其是两个日期相关字段。
  2. 用短间隔测试命令。 临时使用 * * * * *,让任务在一分钟内运行,确认无误后再改回真正的计划。
  3. 保存输出。 在命令后加上 >> /tmp/myjob.log 2>&1,让错误写入文件,而不是直接丢失。
  4. 查看 cron 日志。 在 Debian 和 Ubuntu 上使用 grep CRON /var/log/syslog 或 journalctl -u cron,在 Fedora 和 RHEL 上使用 journalctl -u crond。
  5. 使用绝对路径。 cron 启动任务时只有最基本的环境变量和很短的 PATH,所以在终端里能运行的命令,放到 cron 中可能会失败。

用 Crontab 生成器创建表达式

免费的 Crontab 生成器 完全在浏览器中运行。输入一个表达式,或点击“每 15 分钟”“每天凌晨 2 点”“每周一”等快速预设,工具就会:

通过开关可以选择更详细的描述、24 小时制时间,以及星期编号是否从 0(星期日)开始。它不支持 Quartz 专有的字符(L、W、#),也不支持年份字段。该工具只负责生成和检查表达式;要真正安排任务,请把表达式复制到 crontab -e 或你所用平台的配置中。

常见问题

cron 和 crontab 有什么区别?

cron 是执行计划任务的后台服务(守护进程)。crontab 是“cron table”的缩写,是列出这些任务的文件,每行一个任务:先是 cron 表达式,后面跟着命令。你可以用 crontab -e 编辑自己的 crontab,用 crontab -l 查看它。

cron 任务可以每 30 秒运行一次吗?

标准 cron 不行,它的最小单位是一分钟。带秒字段的调度程序可以做到:Spring 接受 */30 * * * * *,Quartz 则需要在一个日期相关字段中使用 ?,例如 */30 * * * * ?。在传统 cron 中,常见的变通办法是写两行都每分钟运行的任务,其中一行在命令前先执行 sleep 30;。

如何让 cron 任务在每月最后一天运行?

标准 cron 没有表示“最后一天”的符号,但 Quartz 和 AWS 支持在日期字段中使用 L。在传统 cron 中,可以把任务安排在每月 28 日到 31 日,再由命令判断明天是否为 1 日:

0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /path/to/job.sh

date -d tomorrow 是 GNU date 的语法,适用于 Linux。在 macOS 上请改用 date -v+1d。

免费试用:Crontab 生成器 免费使用,无需注册账号。