Linux 定时任务(附学习指南)
Linux 系统的定时任务(Scheduled Tasks)是运维和开发中最常用的自动化手段之一。系统中最核心的两套定时任务工具是 cron(定时器) 和 at(一次性任务队列)。下面从原理到实战逐层展开。
一、cron:周期性定时任务
1. cron 是什么
cron 是一个守护进程(daemon),负责在约定的时间点自动执行用户定义的任务。它对应的服务名在大多数发行版中是 crond,相关的命令和文件包括:
| 命令/文件 | 作用 |
|---|---|
crond |
cron 守护进程 |
crontab |
用户管理自己的定时任务 |
/etc/crontab |
系统级定时任务(注意有用户字段,格式不同) |
/etc/cron.d/ |
系统级定时任务的扩展目录(每个文件类似 /etc/crontab) |
/etc/cron.hourly/、daily/、weekly/、monthly/ |
按周期的脚本目录 |
2. crontab 命令
crontab -l # 查看当前用户的定时任务
crontab -e # 编辑当前用户的定时任务(会用默认编辑器)
crontab -r # 删除当前用户的全部定时任务
crontab -u username # 指定操作某用户的定时任务(需 root)
crontab file # 用文件内容覆盖当前用户的定时任务
3. crontab 的格式(5 个时间字段)
分 时 日 月 周 命令
* * * * * /path/to/command
| 字段 | 含义 | 取值范围 |
|---|---|---|
| 分(minute) | 一小时内的第几分钟 | 0–59 |
| 时(hour) | 一天中的第几小时 | 0–23 |
| 日(day of month) | 一个月中的第几天 | 1–31 |
| 月(month) | 一年中的第几个月 | 1–12 |
| 周(day of week) | 一周中的第几天 | 0–7(0 和 7 都代表星期日) |
关键语法元素:
*:任意值(每分钟/每小时…),:列举多个值,如1,15,30表示第 1、15、30 分钟-:区间,如9-17表示 9 点到 17 点/:步长,如*/5表示每 5 分钟,0-30/10表示在 0–30 分钟内每 10 分钟
注意:"日" 和 "周" 是"或"的关系 —— 两个字段都有限定时,只要其中一个满足就会执行(并非"并且")。
4. 常见例子
# 每天早上 6 点执行备份脚本
0 6 * * * /usr/local/bin/backup.sh
# 每 5 分钟执行一次
*/5 * * * * /usr/local/bin/healthcheck.sh
# 每周一、周三、周五的 8 点和 20 点执行
0 8,20 * * 1,3,5 /usr/local/bin/report.sh
# 每月 1 号凌晨 1 点执行
0 1 1 * * /usr/local/bin/monthly.sh
# 每个工作日上午 9:30 执行
30 9 * * 1-5 /usr/local/bin/workday.sh
5. 系统级 crontab(/etc/crontab)
格式多了一个 用户字段(在"时间"之后、命令之前指定以哪个用户身份运行):
分 时 日 月 周 用户 命令
0 2 * * * root /usr/local/bin/cleanup.sh
6. 环境变量问题(高频坑)
cron 执行时的环境和你登录终端时不完全一样:默认 PATH 很短、没有加载 ~/.bashrc。因此:
- 命令最好写绝对路径;
- 若依赖 PATH,可在脚本顶部或 crontab 里
export PATH=/usr/local/bin:/usr/bin:/bin; - 脚本要可执行(
chmod +x)且首行写#!/bin/bash; - 输出默认不会显示在终端,会通过邮件发送(
MAILTO变量可设置收件人)。
常用环境变量设置:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=admin@example.com
0 2 * * * /usr/local/bin/backup.sh > /tmp/backup.log 2>&1
7. 查看 cron 是否在运行 & 日志
systemctl status crond # 查看服务状态(systemd 系统)
systemctl enable --now crond # 开机自启并启动
# 日志(不同发行版路径不同)
tail -f /var/log/cron # RHEL/CentOS
grep CRON /var/log/syslog # Debian/Ubuntu(旧)
journalctl -u crond # systemd journal
二、at:一次性定时任务
at 用于安排只执行一次的未来任务,适合"5 分钟后再做某事""今晚 11 点关服务器"这类场景。
at 22:00 # 交互式输入命令,Ctrl+D 结束
at now + 5 minutes # 5 分钟后
at 09:00 tomorrow # 明天 9 点
at 12:00 2026-01-01 # 指定具体时间
at midnight / at noon # 午夜 / 正午
常用管理命令:
atq # 查看待执行的一次性任务列表(队列)
atrm <jobid> # 删除指定任务
at 22:00 < script.sh # 通过文件喂给 at
示例:
echo "systemctl stop myservice" | at now + 10 minutes
atq
三、一次性延迟任务:sleep + & (简单替代)
不想装 at、只是"延迟 X 秒后做一次",可以用后台方式:
(sleep 300; /path/to/task.sh) &
缺点:依赖当前 shell 存活、进程挂了任务就没了,只适合临时用。
四、systemd 定时器(timers)—— 现代替代方案
很多新系统推荐用 systemd timer 取代 cron,因为它有更强的依赖管理、日志、随机延迟、失败重试等能力。
1. 需要两个文件
任务单元 foo.service:
[Unit]
Description=My periodic task
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
定时器单元 foo.timer:
[Unit]
Description=Run foo every hour
[Timer]
OnCalendar=*-*-* 00:01:00 # 每天零点一分
OnBootSec=10min # 开机后 10 分钟(可选)
RandomizedDelaySec=2min # 随机延迟,避免负载尖峰
Persistent=true # 错过后补执行
[Install]
WantedBy=timers.target
2. 启用与查看
sudo systemctl start foo.timer
sudo systemctl enable foo.timer
systemctl list-timers # 查看所有定时器及下次触发时间
journalctl -u foo.service # 查看任务执行日志
OnCalendar 语法与 cron 思路类似但更灵活,如:
Mon..Fri *-*-* 09:00:00:工作日早 9 点*:0/15:每 15 分钟daily、hourly、weekly:关键字
五、anacron:处理"关机导致的漏执行"
cron 在关机期间错过的任务不会补跑。anacron 专为解决这一点:开机后检查上次执行时间,如果错过了就补执行。它用于日/周/月度这种低频任务(不处理分钟级)。配置在 /etc/anacrontab。系统里 /etc/cron.daily 等目录其实就是由 anacron 驱动的。
六、实战:完整的定时备份示例
# 1. 编写备份脚本 /usr/local/bin/daily_backup.sh
cat > /usr/local/bin/daily_backup.sh <<'EOF'
#!/bin/bash
export PATH=/usr/local/bin:/usr/bin:/bin
STAMP=$(date +%Y%m%d_%H%M%S)
tar -czf /backup/www_$STAMP.tar.gz -C /var www
find /backup -name 'www_*.tar.gz' -mtime +7 -delete
EOF
chmod +x /usr/local/bin/daily_backup.sh
# 2. 加入 crontab,每天凌晨 2:30 执行,输出写日志
(crontab -l 2>/dev/null; echo "30 2 * * * /usr/local/bin/daily_backup.sh >> /var/log/backup.log 2>&1") | crontab -
crontab -l # 验证
七、常见问题排查清单
- 任务没执行 → 检查服务运行
systemctl status crond;看日志/var/log/cron。 - 执行了但没效果 → 多半是环境问题(PATH、绝对路径、权限)。
- 脚本没执行权限 →
chmod +x script.sh。 - 时间配错 → 日/周是"或"关系,容易误判。
- 输出卡住 → cron 用邮件发输出,可能堆积在
/var/spool/mail。 - 中文/时区 → cron 用的是服务器本地时区,注意
date输出是否与预期一致。
总结对比
| 需求 | 工具 |
|---|---|
| 周期性(分钟/小时/天/周/月) | cron / crontab |
| 一次性、指定未来某个时刻 | at |
| 低频且要补跑错过的任务 | anacron |
| 需要依赖控制、日志、随机延迟、现代系统 | systemd timer |
| 简单临时延迟 | sleep + & |
cron 是最经典、最通用的选择;在较新的 systemd 发行版上,若要更健壮的调度优先考虑 systemd timer。掌握 crontab 五段式语法和 at 的用法,就能覆盖绝大多数定时自动化需求。
Linux 定时任务 · 学习实操指南
目标读者:Linux 初学者到进阶运维。
学习方式:边看边敲,每节都有动手练习。建议准备一台 Linux 虚拟机或云服务器,全程跟着命令走。
0. 环境准备(必须做)
在开始前,确认能执行以下命令且能看到 "running" / "active":
# 检查系统(RHEL/CentOS 和 Ubuntu/Debian 命令略有差异)
cat /etc/os-release
# 确保 crond 服务运行
systemctl status crond # RHEL/CentOS
systemctl status cron # Ubuntu/Debian
若未运行则启动并开机自启:
# RHEL/CentOS
sudo systemctl enable --now crond
# Ubuntu/Debian
sudo systemctl enable --now cron
练习 0:输入 crontab -l,此时应提示"no crontab for <用户名>"(没有任务列表,正常)。记住这个初始状态。
1. 第一课:理解 crontab 五段式
crontab 每行是一个任务,格式为:
分 时 日 月 周 命令
记忆口诀: 五颗星分别代表 分 时 日 月 周(从分钟到年,颗粒度从大到小)。
练习 1:亲手写一个最简单的任务。运行:
crontab -e
在编辑器里加入(每分钟往一个日志里写一行当前时间):
* * * * * echo "tick at $(date)" >> /home/$USER/tick.log
保存退出。等待约 1 分钟后查看:
cat /home/$USER/tick.log
你应该能看到每分钟新增一行。这就是第一个成功的定时任务。✅
💡 注意:命令里的
%(百分号)在 crontab 中有特殊含义,$(date)这类是可以的,但如果你用date +%Y,需要写成\%转义。现在先不深究,后面会讲。
2. 第二课:五个字段的真正含义 + 常用符号
先看一个对照表,然后逐个用手动练习(不急着等时间,先看理论再看例子)。
| 字段 | 含义 | 范围 |
|---|---|---|
| 分 | 每小时的哪一分钟 | 0–59 |
| 时 | 每天的哪个小时 | 0–23 |
| 日 | 每月的哪一天 | 1–31 |
| 月 | 每年的哪个月 | 1–12 |
| 周 | 每周的星期几 | 0–7(0、7=周日) |
三种符号:
,列举多个值-表示区间/表示步长
练习 2a:逐行理解下面每个例子(读懂了再复制执行):
crontab -e
* * * * * echo "每 1 分钟" > /tmp/t1
*/5 * * * * echo "每 5 分钟" > /tmp/t5
0 * * * * echo "每小时整点" > /tmp/tH
30 8 * * * echo "每天 8:30" > /tmp/tD
0 0 * * 1 echo "每周一 零点" > /tmp/tW
0 0 1 * * echo "每月 1 号零点" > /tmp/tM
*/10 9-17 * * * echo "9-17点间每 10 分钟" > /tmp/tWork
1,15,30 12 * * * echo "每天 12:01/12:15/12:30" > /tmp/tList
练习 2b(重要!验证"日/周是或关系"):
写这一行,它表示"每月 1 号 或 每个周日"的 0:00 执行(不是要求同一天):
0 0 1 * 0 echo "月初或周日触发" > /tmp/tOr
🔑 考点:cron 的"日"和"周"只要任意一个匹配就执行,这与直觉不同,是面试和排查的高频坑。
3. 第三课:环境变量与"为什么命令没生效"
cron 运行环境 ≠ 你的登录环境。最常见的坑:
- PATH 不同 ——
python3在终端能用,cron 里报 command not found。 - 不加载 ~/.bashrc —— 依赖的环境变量、alias 都没有。
- 脚本没执行权限。
- 命令没写绝对路径。
练习 3a:制造并解决一个经典错误。
写进 crontab:
* * * * * python3 --version > /tmp/py.txt 2>&1
等一分钟后 cat /tmp/py.txt,不一定报错——很多发行版(尤其 RHEL/CentOS)root 的 cron 会自动注入一个包含 /usr/bin 的默认 PATH,所以 python3 --version 可能正常输出(例如 Python 3.12.9)。成功不代表没问题,关键要看 cron 用的 PATH 和你登录环境是否一致。
做个对比实验,直接看到差异:让 cron 打印它自己的 PATH。
* * * * * echo "PATH=$PATH" > /tmp/py_path.txt 2>&1
等一分钟后 cat /tmp/py_path.txt,再对比你在终端 echo "$PATH" 的结果。典型输出:
# cron 的 PATH(红帽系默认,很短)
PATH=/usr/bin:/bin:/usr/sbin:/sbin
# 你登录终端的 PATH(长很多,含各种自定义目录)
# /root/.nvm/versions/node/v24.19.0/bin:/root/.local/bin:/root/bin:
# /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
对比后你会发现:登录时那些自定义目录(nvm、/usr/local/bin、~/.local/bin 等)在 cron 里全都不存在。
💡 因此
which python3若返回/usr/bin/python3就能在 cron 里跑通;若返回/usr/local/bin/...、/opt/...、~/.nvm/...(非/usr/bin、/bin开头),cron 里大概率就command not found了——这就是"为什么有些命令终端能用、cron 里不能用"的真正原因。
修复(两种方式任选):
# 方式 1:在 crontab 顶部自定义 PATH(推荐,覆盖默认短 PATH)
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
* * * * * python3 --version > /tmp/py.txt 2>&1
# 方式 2:命令写绝对路径(最保险,不依赖 PATH)
* * * * * /usr/bin/python3 --version > /tmp/py.txt 2>&1
(用 which python3 查你的真实路径。)
练习 3b:百分号转义。想记录带日期的时间戳,% 会被 cron 当特殊字符切分:
* * * * * date +"\%Y-\%m-\%d" >> /tmp/dates.txt
试试不转义(去掉 \)和转义两种写法,比较 /tmp/dates.txt 的差异。
4. 第四课:脚本化 —— 把命令写进 .sh 文件
生产环境不推荐把复杂命令直接写 crontab 行,而是写脚本文件。
练习 4:创建并调度一个"日志清理"脚本。
# 1. 创建脚本
cat > ~/cleanup.sh <<'EOF'
#!/bin/bash
export PATH=/usr/local/bin:/usr/bin:/bin
TODAY=$(date +%Y-%m-%d)
echo "[$TODAY] cleanup starts" >> /tmp/cleanup.log
# 删除 /tmp 下 7 天前的 .log 文件
find /tmp -name "*.log" -mtime +7 -delete
echo "[$TODAY] cleanup done" >> /tmp/cleanup.log
EOF
# 2. 加上执行权限
chmod +x ~/cleanup.sh
# 3. 手动试跑一次(确认脚本本身没问题)
~/cleanup.sh && cat /tmp/cleanup.log
# 4. 调度:每天凌晨 3 点跑,输出追加到日志
(crontab -l 2>/dev/null; echo "0 3 * * * $HOME/cleanup.sh") | crontab -
crontab -l # 验证
✅ 核心套路:先手动跑通脚本 → 再交给 cron。这是排查一切定时任务问题的第一原则。
5. 第五课:管理命令 & 系统级 cron
练习 5a:管理命令
crontab -l # 查看
crontab -e # 编辑
crontab -r # 全部删除(小心,没有回收站!)
crontab -r # 想恢复?除非之前备份,否则无解
因此,好习惯是定期备份:
crontab -l > ~/crontab.backup.$(date +%F)
练习 5b:系统级 /etc/crontab(需 root)
系统级文件多了一个"用户"字段,可以用 root 或指定用户运行:
sudo cat /etc/crontab
sudo nano /etc/crontab
加入:
0 2 * * * root /usr/bin/systemctl restart nginx # 示例(若装了 nginx)
6. 第六课:at —— 一次性任务
cron 是周期性的,at 是"只执行一次"。
练习 6a:基本用法
# 2 分钟后执行
echo "echo run-at > /tmp/at_out.txt" | at now + 2 minutes
# 查看队列
atq
# (等 2 分钟后)
cat /tmp/at_out.txt
练习 6b:指定具体时间与删除
# 用文件做任务
cat > ~/do_patch.sh <<'EOF'
#!/bin/bash
echo "patching at $(date)" > /tmp/patch.txt
EOF
# 明天 9 点执行
at 09:00 tomorrow < ~/do_patch.sh
atq # 记下 Job 编号
# 删除刚才那个任务(用 atq 看到的编号)
atrm <编号>
atq # 确认已删除
7. 第七课:anacron —— 补跑漏掉的任务
cron 关机期间的错过不补。anacron 会开机后补执行没跑的任务。
练习 7(理解即可,需 root):查看配置
sudo cat /etc/anacrontab
ls -l /etc/cron.daily # 这些目录实际由 anacron 驱动
观察:1 5 cron.daily run-parts --report /etc/cron.daily —— 意思是"延迟 5 分钟后执行 /etc/cron.daily 里的脚本,若错过则补跑"。
8. 第八课:进阶 —— systemd timer(现代方案,详解)
新系统推荐用 systemd timer:日志友好、支持随机延迟、错过补跑、依赖控制、天然防重叠。下面拆开讲透。
8.1 系统认知:timer 怎么工作
① 两个文件,职责分离。 timer 不像 cron 用一行搞定,而是拆成两个单元:
| 文件 | 回答的问题 | 类比 |
|---|---|---|
xxx.service |
做什么(执行哪条命令) | 一个一次性"作业" |
xxx.timer |
何时做(什么时候触发) | 定时"闹钟" |
它是事件驱动的:xxx.timer 到点后,systemd 唤醒并启动 xxx.service。这种解耦是核心设计。
② 对比 cron 的优点:
| 能力 | cron | systemd timer |
|---|---|---|
| 日志 | 发邮件/自己重定向 | journalctl 自动收集 ✅ |
| 依赖控制 | 无 | Requires= / After= ✅ |
| 任务重叠防护 | 需自己 flock | 天然防止重叠 ✅ |
| 随机延迟 | 无 | RandomizedDelaySec ✅ |
| 错过补跑 | 不补 | Persistent=true ✅ |
| 精确度 | 仅分钟 | 秒/亚秒级 ✅ |
| 环境变量 | 手动设 PATH | Environment= 更规范 ✅ |
③ 常用命令:
systemctl list-timers # 查看所有定时器及下次/上次触发
systemctl start hello.timer # 立即启用(随时可测,不必等真正的时刻)
systemctl enable hello.timer # 开机自启
systemctl status hello.timer
systemctl daemon-reload # 改 unit 文件后必须执行!
systemd-analyze calendar "*:0/1" # 校验 OnCalendar 并预览下次触发时间
systemd-analyze verify hello.timer # 校验 timer 文件语法
④ timer ↔ service 的绑定关系(重点!)
timer 和 service 到底怎么绑到一起?有三种情况要分清:
情况 A:同名默认匹配(最常见,推荐)
xxx.timer 会自动绑定同名的 xxx.service。只要两个文件名前缀一致(都叫 hello),你什么都不用写,timer 到点就会启动 hello.service。
/etc/systemd/system/hello.timer ──┐ 同名前缀 hello
/etc/systemd/system/hello.service ←┘ 自动绑定
⚠️ 如果 timer 和 service 名字不匹配,又不显式指定,timer 到点会去启动"同名但不存在"的 service,然后报错。所以改名时两个文件务必一起改。
情况 B:Unit= 显式指定(解耦,可单点触发任意 service)
在 timer 里用 Unit= 指明要启动哪个 service,名字可以完全不同:
# /etc/systemd/system/backup_all.timer
[Timer]
OnCalendar=*-*-* 02:30:00
Unit=backup_web.service # ← 指定触发这个 service,不再依赖同名
一个 timer 可以触发一个 service,也可以触发多个 service:
[Timer]
OnCalendar=*-*-* 02:30:00
Unit=backup_web.service
Unit=backup_db.service
情况 C:一对多 / 多对一(用依赖关系表达)
-
一个 timer 触发多个 service(一对多):除
Unit=外,更规范的做法是让多个 service 声明Also=或都依赖同一个 timer;但最直白的写法就是在 timer 里写多个Unit=(见情况 B),或让各个 service 通过WantedBy=反向挂到该 timer 的依赖链。示例——两服务都归这个 timer 管:# backup.db.service 只负责 DB # backup.web.service 只负责 WEB # 一个 timer 用多个 Unit= 行同时拉起两个 -
多个 timer 触发同一个 service(多对一):给同一个 service 配多个 timer 即可,例如"每天整点 + 开机后 5 分钟"两种节奏都触发
backup_all.service:# daily_backup.timer [Timer] OnCalendar=*-*-* 00:00:00 Unit=backup_all.service# boot_backup.timer [Timer] OnBootSec=5min Unit=backup_all.service两个 timer 都指向
backup_all.service,任一到点都会触发它。
📌 一句话总结绑定规则:默认靠同名前缀配对;要解耦或不匹配时用
Unit=显式指定;"一个 timer 管多个 service" 用多个Unit=,「多个 timer 管一个 service」就写多个 timer 文件指向同一个Unit=。
⑤ timer 与 systemd 的关系(放对位置)
timer 不是独立软件,而是 systemd(PID 1)自身提供的能力:
- 所有
.timer/.service都是 systemd 的 unit 单元,由/usr/lib/systemd里的 systemd 进程统一加载、调度和管理。 - systemd 实时计算每个 timer 的"下一次触发时间"(
systemctl list-timers显示的 NEXT 列),到点就创建并启动对应 service,并在内存里维护状态(上次运行、是否错过、是否补跑)。 - 因此 timer 依赖 systemd 运行:
systemctl的这些操作本质上是在向 PID 1 发指令。这也是为什么它比 cron 更"系统级"、功能更强(日志、依赖、持久化状态都共用 systemd 的机制)。
8.2 .service(做什么)
[Unit]
Description=My periodic job
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
Environment="PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
User=root
WorkingDirectory=/opt/app
关键点:
Type=oneshot必须写:定时任务是"执行完即结束",不写 oneshot(默认 simple)时 systemd 认为进程常驻,timer 逻辑会错乱。ExecStart=为要执行的命令;多命令可写多行ExecStart=,或全写进脚本。Environment=设环境变量,比 cron 顶部 PATH 更规范。User=/Group=指定运行身份,避免任务都拿 root 权限。
8.3 .timer(何时做)
[Unit]
Description=Run my job
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=2min
[Install]
WantedBy=timers.target
(1) OnCalendar= —— 核心定时语法
格式比 cron 五段式更灵活,形如 "年-月-日 时:分:秒":
| 写法 | 含义 |
|---|---|
*:0/1 |
每 1 分钟 |
*:0/5 |
每 5 分钟 |
*-*-* 02:30:00 |
每天 2:30 |
Mon..Fri *-*-* 09:00:00 |
工作日上午 9 点 |
*-*-1..7 00:00:00 |
每月 1–7 号零点 |
daily / hourly / weekly / monthly |
关键字(天/时/周/月) |
技巧:
- 可用多行
OnCalendar=设多个闹钟,都触发同一个 service。 systemd-analyze calendar "写法"可立即校验并打印今天/下次触发时刻,落盘前先测最稳。- 星期几写在最前面(
Mon..Fri),Mon,Wed,Fri也可。
(2) AccuracySec= —— 触发精度
timer 是省资源导向:默认 AccuracySec=1min,即在计划时刻前后 1 分钟内触发即可,让 systemd 合并多个任务减少唤醒。大多数场景够用;要准点才收窄,如 AccuracySec=1s。
(3) RandomizedDelaySec= —— 随机延迟(防"惊群")
多台机器同时跑同任务(如所有服务器同时备份)会造成负载尖峰。设随机延迟错峰:
RandomizedDelaySec=2min # 计划时刻后再随机延迟 0~2 分钟执行
(4) Persistent= —— 错过补跑(对标 anacron)
Persistent=true
计划时刻机器关机/休眠 → 下次开机立即补跑。只对 OnCalendar 生效,对相对时间定时不生效;默认 false(不补跑,错过就丢)。
(5) 相对时间触发(OnBootSec / OnUnitActiveSec …)
除了"几点"的柱状定时,还支持"相对某事件后多久":
| 指令 | 含义 |
|---|---|
OnBootSec=10min |
开机后 10 分钟 |
OnUnitActiveSec=1h |
上次 unit 开始执行后 1 小时(滚动循环最常用) |
OnUnitInactiveSec=1h |
上次 unit 跑完后 1 小时 |
"每隔 X 执行"经典写法:
[Timer]
OnUnitActiveSec=1h
OnBootSec=5min
8.4 任务重叠防护(实战重点)
cron 中若任务执行时间长于间隔,会重复拉起进程。systemd 中同一 unit 不会并发启动两次:unit 还在运行时再次触发会被跳过。给 Type=oneshot 的 service 配 timer,天然避免重叠跑批。
8.5 日志与调试(比 cron 干净太多)
journalctl -u hello.service # 查看 job 每次执行日志
journalctl -u hello.service -f # 实时跟随
journalctl -u hello.service --since "1 hour ago"
systemctl status hello.service # 最近一次运行状态
systemd-analyze verify hello.service hello.timer # 校验两个 unit
systemd-analyze calendar "*:0/1" # 校验定时语法并预览触发时间
8.6 完整实战:每小时备份 + 随机延迟 + 错过补跑
# 1. 业务脚本
sudo tee /usr/local/bin/hourly_backup.sh > /dev/null <<'EOF'
#!/bin/bash
exec >> /var/log/hourly_backup.log 2>&1
echo "[$(date)] backup start"
tar -czf /backup/hour_$(date +%Y%m%d_%H).tar.gz -C /var www
echo "[$(date)] backup done"
EOF
sudo chmod +x /usr/local/bin/hourly_backup.sh
# 2. service(做什么)
sudo tee /etc/systemd/system/hourly_backup.service > /dev/null <<'EOF'
[Unit]
Description=Hourly website backup
[Service]
Type=oneshot
Environment="PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
ExecStart=/usr/local/bin/hourly_backup.sh
EOF
# 3. timer(何时做)
sudo tee /etc/systemd/system/hourly_backup.timer > /dev/null <<'EOF'
[Unit]
Description=Trigger hourly backup
[Timer]
OnCalendar=*-*-* *:00:00
RandomizedDelaySec=10min
Persistent=true
AccuracySec=1min
[Install]
WantedBy=timers.target
EOF
# 4. 校验 + 启动
sudo systemd-analyze verify hourly_backup.timer
sudo systemctl daemon-reload
sudo systemctl start hourly_backup.timer
sudo systemctl enable hourly_backup.timer
# 5. 查看
systemctl list-timers hourly_backup
journalctl -u hourly_backup.service
8.7 cron vs systemd timer,怎么选?
| 场景 | 推荐 |
|---|---|
| 简单、迁移成本低 | cron |
| 需精确日志 / 依赖控制 / 随机延迟 / 补跑 | systemd timer |
| 需秒级触发 | systemd timer |
| 旧系统 / 传统发行版 | cron |
| 新系统(RHEL 7+/Ubuntu 16.04+ 均内置) | systemd timer |
结论:新项目优先 systemd timer;cron 仍适合简单场景和旧系统,两者可并存。
9. 综合实战项目(推荐完整做一遍)
目标:搭建一个"网站日志每日归档 + 7 天自动清理"系统。
下面前 4 步用 cron(简单直观),第 5 步对比用 systemd timer。
# ========== 1. 模拟一个会生成日志的服务 ==========
mkdir -p ~/webapp/logs
cat > ~/webapp/run_demo.sh <<'EOF'
#!/bin/bash
echo "$(date +%F_%T) request /api from 192.168.1.$RANDOM" >> ~/webapp/logs/app.log
EOF
chmod +x ~/webapp/run_demo.sh
# 模拟每 10 秒产生一条日志(后台 1 分钟,观察即可)
(for i in $(seq 1 6); do ~/webapp/run_demo.sh; sleep 10; done) &
# ========== 2. 归档脚本 ==========
cat > ~/webapp/archive.sh <<'EOF'
#!/bin/bash
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
D=$(date +%F)
mkdir -p ~/webapp/archive
if [ -s ~/webapp/logs/app.log ]; then
mv ~/webapp/logs/app.log ~/webapp/archive/app_${D}.log
echo "[$(date)] archived to app_${D}.log" >> ~/webapp/archive.log
fi
# 删除 7 天前的归档
find ~/webapp/archive -name "app_*.log" -mtime +7 -delete
EOF
chmod +x ~/webapp/archive.sh
# ========== 3. 手动验证脚本 ==========
~/webapp/archive.sh && ls -l ~/webapp/archive/ && cat ~/webapp/archive.log
# ========== 4. 加入 cron:每天 23:59 归档 ==========
(crontab -l 2>/dev/null; echo "59 23 * * * $HOME/webapp/archive.sh") | crontab -
crontab -l | grep archive
# ========== 5. 对比:用 systemd timer 做同样的每日归档 ==========
sudo tee /etc/systemd/system/archive.service > /dev/null <<'EOF'
[Unit]
Description=Daily web log archive
[Service]
Type=oneshot
ExecStart=/home/$USER/webapp/archive.sh
EOF
sudo tee /etc/systemd/system/archive.timer > /dev/null <<'EOF'
[Unit]
Description=Archive daily at 23:59
[Timer]
OnCalendar=*-*-* 23:59:00
Persistent=true
RandomizedDelaySec=60
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl start archive.timer
systemctl list-timers | grep archive
做完后你应该能回答:
- cron 与 systemd timer 各自优缺点是什么?
- 归档脚本为什么要"先手动跑通再接调度"?
- 如果某天服务器关机了,这个每日归档会丢失还是会补跑?(提示:Persistent=true)
10. 排错手册(照单排查)
| 现象 | 排查方向 |
|---|---|
| 任务完全没执行 | ① crond 在跑吗:systemctl status crond;② 看日志:grep CRON /var/log/syslog 或 cat /var/log/cron 或 journalctl -u crond |
| 执行了但没效果 | 先手动跑脚本看报错;查 PATH、绝对路径、权限 chmod +x |
| 只有部分命令生效 | 多行命令建议写进 .sh 再调用 |
| 中文乱码/时区不对 | cron 用服务器本地时区;date 看时间,必要时设 TZ 环境变量 |
| 邮件堆积 | cron 默认把输出发邮件(MAILTO 可改);无邮件服务会堆积在 /var/spool/mail |
| 脚本在错误环境执行 | SHELL=/bin/bash、PATH=... 写在 crontab 顶部 |
| 想立刻看到执行效果 | 用 * * * * *(每分钟)测试,而不是等天/周 |
最高效的调试步骤(记住这 4 步):
- 手动执行脚本,确认无报错;
- 用
* * * * *让任务每分钟跑一次; - 命令输出重定向到日志
>> /tmp/xxx.log 2>&1; - 等 60 秒后查看日志和系统 cron 日志。
11. 自测题(检验掌握程度)
选择题
- crontab 五段式
* * * * *依次代表什么?- A. 分 时 日 月 周
- B. 时 分 日 月 周
- C. 分 时 周 月 日
*/10 * * * *表示?- A. 每小时第 10 分钟
- B. 每 10 分钟
- C. 每天 10 点
- cron 里"日"和"周"字段的关系是?
- A. 且(必须同时满足)
- B. 或(任一满足即执行)
- at 和 cron 的区别是?
- A. at 周期性,cron 一次性
- B. cron 周期性,at 一次性
- C. 无区别
实操题
- 写一行 crontab:每周五下午 18:00 运行
/opt/backup.sh。 - 让系统每天 00:30 清理
/tmp下 3 天前的*.tmp文件(需写脚本并调度)。 - 用 at 安排"10 分钟后重启一个叫 demo 的服务"。
(答案在本指南末尾)
12. 速查卡片
# ---------- crontab ----------
crontab -l | -e | -r | -u user
# 格式: 分 时 日 月 周 命令
0 6 * * * /path/script.sh # 每天 6 点
*/5 * * * * /path/x.sh # 每 5 分钟
0 9-17 * * 1-5 /path/x.sh # 工作日 9-17 点整点
PATH=/usr/local/bin:/usr/bin:/bin # 顶部设置 PATH
# ---------- at ----------
echo "cmd" | at now + 1 hour
echo "cmd" | at 09:00 tomorrow
atq # 查看
atrm <id> # 删除
# ---------- systemd timer ----------
sudo systemctl start x.timer
sudo systemctl enable x.timer
systemctl list-timers
# 文件: /etc/systemd/system/x.{service,timer}
13. 自测题答案
-
A
-
B
-
B(日/周是"或")
-
B
-
0 18 * * 5 /opt/backup.sh(注意周字段:5 = 周五) -
示例:
# /home/user/clean_tmp.sh #!/bin/bash find /tmp -name '*.tmp' -mtime +3 -delete # crontab 行 30 0 * * * /home/user/clean_tmp.sh -
echo "systemctl restart demo" | at now + 10 minutes
14. 下一步学习建议
- ✅ 已掌握 cron 五段式、at、anacron 概念、systemd timer 基础 → 你已经能胜任大多数日常定时任务。
- 🔍 进阶方向:cron 日志分析、
run-parts、flock防止任务重叠、RandomizedDelaySec在生产集群的应用、用 Ansible 批量下发 crontab。 - 🧪 安全注意:不要给不信任的脚本加 crontab;涉及删除的命令务必先
find ... -print预览再-delete。
核心心法:先手动跑通,再交给调度;永远把输出写进日志;用最短周期(每分钟)验证。