适用:所有 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:更现代的方式

为什么用它

cronsystemd 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

六、⚠️ 危险提醒

定时任务会自动执行你的命令。写错了没人拦你。

三条规矩:

  1. 先在命令行手动跑一遍,确认命令正确
  2. 危险命令(rm、find -delete)先在测试目录验证
  3. 一定要重定向日志,出问题能查

特别是 find ... -delete:

# 先看看会删什么(不删)
find /tmp -type f -atime +7 -print

# 确认没问题,再加 -delete
find /tmp -type f -atime +7 -delete

-delete 不可撤销。 先 -print 看一遍,是必须养成的习惯。


七、动手:五个练习

练习 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 能补跑。

危险命令先 -print 看一遍。


← 上一篇 S4|反向代理与 HTTPS 下一篇 → S6|监控:出问题前先知道