适用:所有 Linux 耗时:约 25 分钟 前置:E6(会写脚本)
让机器自己干活。 这一篇讲两种方式:cron(经典)和 systemd timer(现代)。
一、cron:最经典的方式
基本用法
crontab -e # 编辑当前用户的定时任务
crontab -l # 列出
crontab -r # 删除全部 ⚠️ 不问确认
第一次会问用哪个编辑器。选 nano(否则进了 vim 出不来)。
格式:五个星号
分 时 日 月 周 命令
* * * * * /path/to/command
| 位置 | 字段 | 范围 |
|---|---|---|
| 1 | 分钟 | 0-59 |
| 2 | 小时 | 0-23 |
| 3 | 日 | 1-31 |
| 4 | 月 | 1-12 |
| 5 | 星期 | 0-7(0 和 7 都是周日) |
常用写法
0 2 * * * 每天凌晨 2:00
*/5 * * * * 每 5 分钟
0 * * * * 每小时整点
0 9 * * 1 每周一 9:00
0 0 1 * * 每月 1 号 0:00
30 3 * * 1-5 工作日 3:30
0 8,20 * * * 每天 8:00 和 20:00
记忆法:“分 时 日 月 周”,从左到右粒度由细到粗(除了周在最后)。
三个必踩的坑
坑 1:cron 的 PATH 很短
cron 里的环境变量和你登录时不一样。 python3、node 可能找不到。
# ❌ 可能失败
0 2 * * * python3 /home/user/script.py
# ✅ 用绝对路径
0 2 * * * /usr/bin/python3 /home/user/script.py
# ✅ 或者显式设 PATH
0 2 * * * PATH=/usr/local/bin:/usr/bin:/bin /home/user/script.sh
查命令的绝对路径:
which python3
which node
坑 2:不重定向输出 = 收不到任何信息
# ❌ 出错了你完全不知道
0 2 * * * /home/user/backup.sh
# ✅ 记录日志
0 2 * * * /home/user/backup.sh >> /home/user/backup.log 2>&1
2>&1 是把错误输出也合并到标准输出,不然错误信息会变成邮件(如果配了 MTA)或直接消失。
坑 3:% 在 crontab 里有特殊含义
% 表示换行。 如果命令里有 %(比如 date +%Y-%m-%d),必须转义成 \%:
# ❌ 会被截断
0 2 * * * /bin/echo $(date +%Y-%m-%d) > /tmp/d.txt
# ✅ 转义
0 2 * * * /bin/echo $(date +\%Y-\%m-\%d) > /tmp/d.txt
更好的办法:把逻辑写进脚本,crontab 只调脚本:
0 2 * * * /home/user/daily.sh >> /home/user/daily.log 2>&1
脚本里随便用 %,不受影响。
一个完整的 cron 例子
# 每天 2:00 备份
0 2 * * * /home/zhangsan/bin/backup.sh >> /home/zhangsan/.backup.log 2>&1
# 每 10 分钟检查磁盘
*/10 * * * * /home/zhangsan/bin/diskcheck.sh >> /home/zhangsan/.disk.log 2>&1
# 每周日 3:00 清理临时文件
0 3 * * 0 /usr/bin/find /tmp -type f -atime +7 -delete >> /home/zhangsan/.clean.log 2>&1
二、systemd timer:更现代的方式
为什么用它
| cron | systemd timer | |
|---|---|---|
| 查日志 | 翻文件 | journalctl -u |
| 错过执行 | 跳过(机器关着就没了) | 能补跑(Persistent=true) |
| 依赖管理 | 无 | 能等网络就绪 |
| 随机延迟 | 无 | 有 |
| 资源限制 | 无 | 有(内存/CPU) |
| 上手难度 | 简单 | 稍复杂 |
简单任务用 cron,要"可靠 + 可观测"用 timer。
结构:两个文件
service 文件(做什么):
sudo nano /etc/systemd/system/backup.service
[Unit]
Description=Daily backup
[Service]
Type=oneshot
User=zhangsan
ExecStart=/home/zhangsan/bin/backup.sh
StandardOutput=journal
StandardError=journal
timer 文件(什么时候做):
sudo nano /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 2am
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
启用
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
# 查看
systemctl list-timers | grep backup
systemctl status backup.timer
立即手动跑一次(测试)
sudo systemctl start backup.service
journalctl -u backup.service -n 30 --no-pager
OnCalendar 写法
OnCalendar=*-*-* 02:00:00 每天 2:00
OnCalendar=*-*-* *:00:00 每小时整点
OnCalendar=Mon *-*-* 09:00:00 每周一 9:00
OnCalendar=*-*-01 03:00:00 每月 1 号 3:00
OnCalendar=daily 每天 0:00
OnCalendar=weekly 每周一 0:00
测试写法对不对:
systemd-analyze calendar "*-*-* 02:00:00"
它会告诉你怎么解析的——这是排查"为什么没跑"的利器。
Persistent=true 是什么
如果定时点机器关着,开机后会补跑一次。
cron 不会补——错过就错过了。
对备份来说这很重要:机器晚上关着,cron 的凌晨 2 点备份永远不会执行。
三、怎么选
简单、固定周期、机器常开?
└→ cron(三行搞定)
需要日志可查、需要补跑、需要等网络就绪?
└→ systemd timer
要跑很多次(每分钟)?
└→ systemd timer(cron 每分钟刷日志很吵)
我的建议:第一次学 cron,因为到处都有。有重要任务了再上 timer。
四、调试:为什么没跑
cron 排查
# 1. 任务真的加进去了吗
crontab -l
# 2. cron 服务在跑吗
systemctl status cron
# 3. 看 cron 的日志(重要)
grep CRON /var/log/syslog | tail -20
# 或
journalctl -u cron -n 30 --no-pager
日志里会显示"什么时候执行了哪个命令"。 如果完全没有记录,说明 cron 没读到你的任务。
systemd timer 排查
# 1. timer 是活跃的吗
systemctl list-timers | grep 名字
systemctl status 名字.timer
# 2. 下次什么时候跑
systemctl list-timers --all
# 3. 上次跑成功了吗
systemctl status 名字.service
journalctl -u 名字.service -n 50 --no-pager
# 4. 语法解析对不对
systemd-analyze calendar "你的OnCalendar"
五、一个真实场景:自动清理过期文件
需求:每天清掉 /tmp 里 7 天没访问的文件。
用 cron
crontab -e
0 3 * * * /usr/bin/find /tmp -type f -atime +7 -delete >> /home/user/.clean.log 2>&1
用 timer
# /etc/systemd/system/cleantmp.service
[Unit]
Description=Clean old tmp files
[Service]
Type=oneshot
ExecStart=/usr/bin/find /tmp -type f -atime +7 -delete
# /etc/systemd/system/cleantmp.timer
[Unit]
Description=Daily tmp cleanup
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now cleantmp.timer
六、⚠️ 危险提醒
定时任务会自动执行你的命令。写错了没人拦你。
三条规矩:
- 先在命令行手动跑一遍,确认命令正确
- 危险命令(
rm、find -delete)先在测试目录验证 - 一定要重定向日志,出问题能查
特别是 find ... -delete:
# 先看看会删什么(不删)
find /tmp -type f -atime +7 -print
# 确认没问题,再加 -delete
find /tmp -type f -atime +7 -delete
-delete不可撤销。 先
七、动手:五个练习
练习 1:写一个每分钟跑的 cron(测试用)
crontab -e
* * * * * /bin/date >> /tmp/cron-test.log
等两分钟,然后:
cat /tmp/cron-test.log
看到时间戳就成功了。
测完记得删掉这条(不然每分钟都写)。
练习 2:验证 PATH 问题
# 故意用 python3 而不是绝对路径
* * * * * python3 -c "print('ok')" >> /tmp/py-test.log 2>&1
看日志里有没有报错。有些系统上会失败——这就是 PATH 坑。
练习 3:写一个 systemd timer
按第二节的 backup 例子,写 service + timer,启用并手动测试。
练习 4:验证 calendar 语法
systemd-analyze calendar "*-*-* 02:00:00"
systemd-analyze calendar "Mon *-*-* 09:00:00"
systemd-analyze calendar "daily"
它会告诉你"下次执行时间"——这是验证写法的好方法。
练习 5:安全的删除演练
# 建几个测试文件
mkdir -p /tmp/cleantest
touch /tmp/cleantest/{a,b,c}.txt
# 先 -print 看
find /tmp/cleantest -type f -atime +0 -print
# 确认后再删
find /tmp/cleantest -type f -atime +0 -delete
ls /tmp/cleantest
八、验证:你过关了吗
- 会
crontab -e并写对五个星号 - 知道 cron 的 PATH 很短,要用绝对路径
- 知道要
>> log 2>&1 - 知道
%要转义(或干脆写进脚本) - 会写 systemd service + timer 两个文件
- 会用
systemctl list-timers看下次执行 - 知道
Persistent=true能补跑 - 会用
-print验证find -delete的目标
九、不行怎么办
Q1:cron 任务没执行 按顺序查:
crontab -l # 1. 任务在吗
systemctl status cron # 2. 服务在跑吗
grep CRON /var/log/syslog | tail # 3. 有没有执行记录
Q2:执行了但命令失败 没重定向日志,所以你看不到错误。 加上:
>> /tmp/xxx.log 2>&1
Q3:脚本手动跑正常,cron 里失败 99% 是环境变量:
- PATH 不同 → 用绝对路径
- 工作目录不同 → 脚本开头
cd /正确路径
打印环境看看:
* * * * * env > /tmp/cron-env.txt
Q4:crontab -r 手滑删了全部任务
没有回收站。 只能重建。
建议:把 crontab 备份到文件:
crontab -l > ~/crontab.bak
Q5:systemd timer 启用了但没跑
systemctl list-timers --all | grep 名字 # 看 NEXT 时间
systemd-analyze calendar "你的OnCalendar" # 验证语法
journalctl -u 名字.timer -n 30 # 看 timer 日志
Q6:timer 跑了但 service 失败
journalctl -u 名字.service -n 50 --no-pager
Q7:改了 timer 文件不生效
sudo systemctl daemon-reload
sudo systemctl restart 名字.timer
Q8:每分钟跑,日志文件涨得很快 加清理(见第五节),或者改成每 5 分钟。
Q9:想跑"开机后 5 分钟"
[Timer]
OnBootSec=5min
cron 做不到这个(除非用 @reboot + sleep)。
十、一句话总结
cron:
分 时 日 月 周 命令,用绝对路径,必须重定向日志。systemd timer:两个文件(service + timer),日志用
journalctl -u,Persistent=true能补跑。危险命令先
← 上一篇 S4|反向代理与 HTTPS 下一篇 → S6|监控:出问题前先知道