Cron 是什么?从历史说起
Cron 是 Unix 世界中历史最悠久、最经久不衰的工具之一。它由 Ken Thompson 在 1979 年随 Unix V7(第七版 Unix)在贝尔实验室发布。"cron" 这个名字来源于希腊语 chronos(χρόνος),意为"时间"。
最初的 cron 守护进程每分钟唤醒一次,扫描任务列表,执行时间匹配的命令。这种"时间表达式 + 命令"的设计极其简洁,却无比高效,以至于在此后的四十多年里几乎没有发生根本性变化。如今,Linux 发行版普遍使用 Vixie Cron(Paul Vixie,1988 年)、cronie 等改进版本,macOS 则采用 launchd。Cron 的影响还延伸到应用层:Java 的 Quartz Scheduler、Python 的 APScheduler、Node.js 的 node-cron,以及 AWS EventBridge、Google Cloud Scheduler 等云服务,都沿用了 cron 表达式语法。
标准 5 字段 Cron 语法
标准 cron 表达式由 5 个字段组成,以空格分隔:
┌───────── 分钟 (0–59)
│ ┌─────── 小时 (0–23)
│ │ ┌───── 日期 (1–31)
│ │ │ ┌─── 月份 (1–12 或 JAN–DEC)
│ │ │ │ ┌─ 星期 (0–7,0 和 7 均表示周日,或 SUN–SAT)
│ │ │ │ │
* * * * * 命令
各字段详解
| 字段 | 允许值 | 说明 |
|---|---|---|
| 分钟 | 0–59 | 任务在每小时第几分钟触发 |
| 小时 | 0–23 | 24 小时制,0 为午夜 |
| 日期 | 1–31 | 每月的第几天 |
| 月份 | 1–12 或 JAN–DEC | 一年中的月份 |
| 星期 | 0–7 或 SUN–SAT | 0 和 7 均代表周日 |
注意: 当"日期"和"星期"字段同时非 * 时,cron 使用 OR 逻辑——满足任意一个条件就会执行,这常让初学者感到困惑。
扩展 6 字段语法(含秒)
Quartz Scheduler(Java)、Spring Framework 的 @Scheduled 注解以及部分云平台在最前面增加了一个秒字段:
┌─────────── 秒 (0–59)
│ ┌───────── 分钟 (0–59)
│ │ ┌─────── 小时 (0–23)
│ │ │ ┌───── 日期 (1–31)
│ │ │ │ ┌─── 月份 (1–12)
│ │ │ │ │ ┌─ 星期 (1–7,Quartz 中 1=周日)
│ │ │ │ │ │
0 * * * * ?
注意:Quartz 的星期字段编号不同(1=周日,7=周六),与 Unix cron 的习惯相反,使用时需格外小心。
特殊字符详解
* — 任意值
匹配该字段的所有可能值。* * * * * 表示每分钟执行一次。
, — 值列表
指定多个离散值。0 9,12,18 * * * 表示每天 9:00、12:00、18:00 各执行一次。
- — 范围
指定连续范围。0 9-17 * * 1-5 表示周一至周五每天 9 时到 17 时,每小时整点执行。
/ — 步长
*/n 表示"每隔 n 个单位"。*/15 * * * * 表示每 15 分钟执行一次;0 */2 * * * 表示每隔 2 小时执行一次。也可与范围组合:10-50/10 表示 10、20、30、40、50。
? — 不指定(仅 Quartz/Spring)
用于"日期"或"星期"字段,表示"不关心"。由于同时指定两者会产生歧义,其中一个必须写 ?。
L — 最后(仅 Quartz/Spring)
在"日期"字段中表示该月最后一天;在"星期"字段中表示该月最后一个对应星期几。0 0 L * ? 表示每月最后一天午夜执行。0 0 ? * 6L 表示每月最后一个周五午夜执行。
W — 最近工作日(仅 Quartz/Spring)
15W 表示距 15 日最近的工作日。若 15 日是周六,则执行日为 14 日(周五);若是周日,则执行日为 16 日(周一)。
# — 第 N 个星期几(仅 Quartz/Spring)
2#3 表示每月第 3 个周二。格式为 <星期几>#<第几次>。0 10 ? * 2#1 表示每月第一个周一 10:00 执行。
特殊时间别名
大多数 cron 实现支持以下便捷别名:
| 别名 | 等价表达式 | 含义 |
|---|---|---|
@yearly |
0 0 1 1 * |
每年 1 月 1 日午夜 |
@annually |
0 0 1 1 * |
同 @yearly |
@monthly |
0 0 1 * * |
每月 1 日午夜 |
@weekly |
0 0 * * 0 |
每周日午夜 |
@daily |
0 0 * * * |
每天午夜 |
@midnight |
0 0 * * * |
同 @daily |
@hourly |
0 * * * * |
每小时第 0 分钟 |
@reboot |
— | 系统启动后执行一次 |
常用 Cron 表达式速查表
| 表达式 | 含义 |
|---|---|
* * * * * |
每分钟 |
0 * * * * |
每小时整点 |
*/15 * * * * |
每 15 分钟 |
0 0 * * * |
每天午夜 |
30 2 * * * |
每天凌晨 2:30 |
0 9-17 * * 1-5 |
工作日每天 9:00–17:00 整点 |
0 0 * * 0 |
每周日午夜 |
0 0 1 * * |
每月 1 日午夜 |
0 0 1 1 * |
每年 1 月 1 日午夜 |
0 6 * * 1-5 |
工作日每天 6:00 |
0 */6 * * * |
每隔 6 小时 |
Linux 上的 Crontab 操作
# 编辑当前用户的 crontab
crontab -e
# 查看当前用户的 crontab
crontab -l
# 删除当前用户的 crontab
crontab -r
# 以 root 身份编辑其他用户的 crontab
crontab -u username -e
典型 crontab 示例
# 设置环境变量
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
[email protected]
# 每天凌晨 2:30 执行备份
30 2 * * * /usr/local/bin/backup.sh
# 每周日午夜执行清理,并记录日志
0 0 * * 0 /usr/local/bin/cleanup.sh >> /var/log/cleanup.log 2>&1
# 每 15 分钟执行健康检查
*/15 * * * * /usr/local/bin/health-check.sh
# 指定时区执行(需要 GNU cron 或 cronie 支持)
CRON_TZ=Asia/Shanghai
0 9 * * 1-5 /usr/local/bin/morning-report.sh
系统级 Cron 目录
/etc/crontab— 系统级 crontab,每行多一个用户名字段/etc/cron.d/— 软件包和服务的单独 cron 配置文件/etc/cron.daily/、/etc/cron.hourly/、/etc/cron.weekly/、/etc/cron.monthly/— 放入这些目录的脚本会由run-parts自动按频率执行
macOS 与 Windows 上的调度
macOS:launchd
macOS 已将 launchd 作为推荐的调度机制,通过 .plist 文件定义任务,存放在 ~/Library/LaunchAgents/(用户任务)或 /Library/LaunchDaemons/(系统任务)。不过,cron 在 macOS 上依然可用,许多开发者仍习惯使用它。
Windows:任务计划程序
Windows 使用任务计划程序(Task Scheduler),通过 schtasks 命令或 GUI 操作。WSL(Windows Subsystem for Linux)则提供了完整的 Linux cron 环境。
应用层 Cron 调度器
Node.js — node-cron
const cron = require('node-cron');
// 每天凌晨 2:30 执行,指定时区
cron.schedule('30 2 * * *', async () => {
await runDailyBackup();
}, {
timezone: "Asia/Shanghai"
});
Python — APScheduler
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
scheduler = BlockingScheduler()
@scheduler.scheduled_job(CronTrigger.from_crontab('0 9 * * 1-5'))
def morning_job():
print("工作日早上 9 点,开始执行任务。")
scheduler.start()
云平台与容器调度
AWS EventBridge Scheduler
AWS EventBridge 支持 6 字段 cron 表达式(UTC),日期和星期字段其中一个必须为 ?:
cron(0 2 * * ? *) # 每天 UTC 凌晨 2 点
Google Cloud Scheduler
使用标准 5 字段 Unix cron 语法,支持通过控制台或 API 设置时区。
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-backup
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: backup-image:latest
restartPolicy: OnFailure
concurrencyPolicy: Forbid 可防止上一次任务尚未完成时新任务重叠启动。
时区处理
Cron 默认使用系统时区,服务器通常设置为 UTC。这是最常见的 bug 来源之一——在 UTC 服务器上设定 0 9 * * * 实际上是 UTC 9 点,换算成北京时间(UTC+8)是下午 17:00。
时区最佳实践:
- 在 crontab 中使用
CRON_TZ=明确指定时区 - 云平台(如 GCP Cloud Scheduler、Kubernetes
spec.timeZone)使用专用时区字段 - 以固定 UTC 时间调度,避免夏令时(DST)切换带来的问题——
0 2 * * *在夏令时变更日可能跳过或重复执行 - 在文档和注释中始终注明表达式对应的时区
安全注意事项
Cron 任务通常以较高权限运行,是常见的攻击面,需特别注意:
最小权限原则: 以能完成任务的最低权限用户运行,避免使用 root。
不硬编码凭据: 不要在 crontab 或脚本中直接写入密码、API 密钥等敏感信息。使用权限受限的环境变量文件(chmod 600)或密钥管理服务。
脚本权限管理: 确保 cron 脚本不可被其他用户写入。
chmod 750 /usr/local/bin/backup.sh
chown root:staff /usr/local/bin/backup.sh
监控与告警: 使用 MAILTO= 接收执行输出,并结合 Healthchecks.io 或 Cronitor 等"死亡开关"服务,在任务未按时执行时发出告警。
审计日志: 将所有 cron 任务的执行记录写入日志,便于故障排查和合规审计。
最佳实践
任务幂等性: 确保任务多次执行的结果与执行一次相同,便于从故障中恢复。
使用文件锁防止重叠: 如果任务执行时间可能超过触发间隔,使用 flock:
*/5 * * * * flock -n /var/lock/myjob.lock /usr/local/bin/myjob.sh
显式重定向输出: 避免 cron 将输出发送邮件或静默失败:
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
部署前验证表达式: 使用 Cron Parser 工具验证表达式是否符合预期,查看未来 5 次执行时间。
版本控制 crontab: 将 crontab 文件纳入 Git,通过 Ansible 或 Puppet 统一部署,避免"配置漂移"。
常见问题解答
Q:Cron 任务不执行,怎么排查?
最常见的原因:①脚本没有执行权限;②脚本依赖当前 Shell 环境变量,而 cron 环境极简;③命令路径问题——cron 不加载你的 Shell 配置,建议所有命令写绝对路径;④cron 守护进程未运行(systemctl status cron 或 systemctl status crond)。
Q:*/5 和 0/5 有什么区别?
在标准 Unix cron 中,*/5 表示从 0 开始每 5 分钟(0、5、10…55)。0/5 是 Quartz/AWS EventBridge 的写法,含义相同。
Q:Cron 能每秒执行一次吗?
标准 cron 的最小粒度是 1 分钟。需要亚分钟级调度,可使用 Quartz、APScheduler,或 systemd timer(OnCalendar=*:*:00/10),也可以让 cron 任务内部循环执行。
Q:如何在每月最后一天执行任务?
标准 cron 没有原生的"最后一天"操作符。常见方案:判断明天是否为 1 日来间接实现。Quartz cron 支持在日期字段直接写 L。
Q:0 0 * * 0 和 @weekly 有什么区别?
两者完全等价,都表示每周日午夜执行。@weekly 只是大多数现代 cron 实现提供的助记别名。
Q:在终端运行正常的脚本,为什么在 cron 中失败?
Cron 运行环境极简(通常只有 HOME、LOGNAME、PATH=/usr/bin:/bin、SHELL=/bin/sh),不加载 .bashrc 或 .profile。建议脚本首行写 #!/bin/bash,添加 set -e 在出错时退出,使用绝对路径,并将 stdout/stderr 全部重定向到日志文件。
总结
Cron 自 1979 年诞生以来,一直是 Unix 自动化调度的基石,并在云时代焕发了新的生命力。无论是管理单台 Linux 服务器、编排 Kubernetes 工作负载,还是配置云端事件触发,深入理解 cron 表达式都能帮助你避免大量调试工作,防止因调度失误带来的生产故障。
核心要点:
- 5 字段标准语法覆盖绝大多数场景
/表示步长,,表示列表,-表示范围- 服务器优先使用 UTC,或明确指定
CRON_TZ - 遵守最小权限、不硬编码凭据、做好监控等安全原则
- 上线前用解析工具验证表达式,避免低级错误
概述
Cron 任务是现代自动化的核心,但其语法即便对于经验丰富的系统管理员来说也可能令人生畏。我们的在线 Cron 解析器旨在消除调度中的盲目性。通过将晦涩的 Cron 表达式翻译成清晰、易懂的语言,该工具可确保您的计划任务完全按照您的意图运行。无论您是在管理备份、清理脚本还是自动化电子邮件,准确性都至关重要。
核心功能
- 实时翻译: 立即以纯文本(或您选择的语言)查看 Cron 表达式的含义。
- 下次运行时间: 查看未来 5 次或更多次计划执行时间的列表,以验证您的逻辑。
- 支持所有字段: 处理标准 Cron 格式,包括秒(6 字段)和年(7 字段)变体。
- 语法高亮: 视觉提示可帮助您识别分钟、小时、日、月和星期。
使用指南
- 输入: 在主字段中粘贴或键入您的 Cron 表达式。
- 观察: 易于理解的翻译会随着您的键入自动更新。
- 验证: 检查“下次执行时间”列表以确保它符合您的要求。
- 复制: 如果您从头开始,请使用提供的示例。
常见应用场景
- 服务器维护: 调度每晚数据库备份或日志轮转。
- Web 开发: 在 Laravel 或 Django 等框架中设置循环后台任务。
- DevOps: 配置 CI/CD 流水线和自动化健康检查。
- 个人生产力: 管理本地计算机上的循环提醒或脚本执行。
技术背后的原理
Cron 表达式由 5 到 7 个由空格分隔的字段组成。每个字段代表一个时间单位:分钟、小时、月中某天、月份和周中某天。我们的解析器使用强大的逻辑引擎,可以处理特殊字符,如 *(任意)、,(列表)、-(范围)、/(增量)和 L(最后)。它通过考虑闰年和月份长度来准确计算下一次发生的时间。
常见问题解答
- 它支持秒吗? 是的,它支持包含秒的 6 字段 Cron 表达式。
- 它能处理像 @daily 这样的非标准别名吗? 是的,支持常见的别名。
- 它支持时区吗? 默认情况下,计算基于 UTC,但您可以相应地调整您的视角。
使用限制
- 实现差异: 不同的系统(例如 Quartz、AWS、Jenkins)在 Cron 语法上有细微差别;请始终在您的特定平台上进行验证。
- 边缘情况: “每月最后一天”(L)和“最近的工作日”(W)的行为可能因操作系统而异。