首页/系统/Linux 运维命令速查/cron 实现每月最后一天等复杂日历
定时任务与计划50 条

cron 实现每月最后一天等复杂日历

cron 五字段表达不了「最后一天」「第一个工作日」:用日期判断、多行组合或交给 systemd OnCalendar 的 ~ 语义。

Linux 运维命令速查系统更新于 2026-10-13

cron 表达不了的日历规则

cron 的五个时间字段表达不了“每月最后一天”“每月第一个工作日”这类规则,它只能按固定的日或星期匹配。常见解法有三种:写一行每天都跑的判断(用 date 判断明天是不是 1 号,等价于今天是月末);用 28 到 31 日的多值匹配后在脚本里做幂等去重;或者改用 systemd timer 的 OnCalendar,其中 ~ 表示当月最后一天。把判断逻辑放进脚本最通用,也不受系统特性限制。

基本信息

适用平台任意 cron(判断式与多值式);systemd 的 ~ 语义需要 236 及以上版本
核心手法date 判断、多值加判断、OnCalendar
关键命令date -d tomorrow +%d、date +%d
跨平台差异GNU date 用 -d,BSD/macOS 用 -v1d
硬性要求多值触发时脚本必须幂等

典型使用场景

  • 每月最后一天出账单或月度报表
  • 月末做归档、月结与上月数据清理
  • 每月第一个工作日执行对账
  • 季度末做容量评估与汇总
  • 任何按日历边界而非固定日期触发的任务

三种写法

三种写法
# 每天 8 点跑,仅在“明天是 1 号”时执行(即今天是月末)
# crontab 中的百分号需要转义
0 8 * * * [ "$(date -d tomorrow +\%d)" = 01 ] && /opt/scripts/month-end.sh

# 多值覆盖 28 到 31 日,脚本内部再做月末判断与幂等控制
0 8 28-31 * * [ "$(date -d tomorrow +\%d)" = 01 ] && /opt/scripts/month-end.sh

# 手动验证判断逻辑(不改 crontab)
date -d tomorrow +%d
[ "$(date -d tomorrow +%d)" = 01 ] && echo '今天是月末'

# systemd timer 的写法(需要 236 及以上版本)
# OnCalendar=*-*-~1 08:00:00        # ~ 表示当月最后一天

三种解法对照

解法怎么写优缺点
每日判断式每天都跑,脚本内判断明天是否为 1 号简单通用;每天启动一次但通常立刻退出
多值匹配式日字段写 28-31 再加判断触发次数少;必须幂等,否则可能一个月跑多次
OnCalendar ~ 语义systemd timer 直接表达最后一天语法简洁;依赖 systemd 版本
判断依据date -d tomorrow +%d 是否为 01GNU date 特性,跨平台需换写法
幂等保障脚本内加锁或状态标记多值触发时的必备措施
同类需求(每月第一个周一、每月第几个工作日)思路一致:让 cron 负责按天或按周唤醒,把日历判断放进脚本,这样最通用,也不受 systemd 版本限制。
date -d 是 GNU coreutils 的写法,BSD 与 macOS 上要用 date -v+1d:跨平台脚本必须区分,否则判断恒为假,任务配置看着完全正确却从不执行。

易错点

  • date -d 写法跨平台不兼容:BSD/macOS 需要 -v 语法,否则月末判断永远为假、任务静默不执行
  • 用 28-31 多值触发却不做幂等:一个月可能执行多次,账单或归档类任务会重复处理数据
  • 时区与时间同步不一致:date 的判断时区与调度器不同会让边界错位,跨时区机器先对齐时间
  • 把 crontab 里的百分号当普通字符:crontab 文件中 % 需要转义,否则命令会被截断

数据来源与参考

  • man 5 crontab(无法表达的点)
  • systemd.time(7):~ 语义
  • man 1 date

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌