前言

备份、日志切割、证书续期、数据同步……运维一半的自动化都靠定时任务。这篇把 cron 用法与环境陷阱一次讲清。

一、crontab 基础

crontab -e      # 编辑当前用户的定时任务
crontab -l      # 列出
crontab -r      # 删除全部(危险! 慎用)
ls /var/spool/cron/       # 各用户的任务文件

五段表达式:

┌──────── 分钟 (0-59)
│ ┌────── 小时 (0-23)
│ │ ┌──── 日   (1-31)
│ │ │ ┌── 月   (1-12)
│ │ │ │ ┌ 星期 (0-7, 0和7都是周日)
│ │ │ │ │
* * * * *  要执行的命令
# 常用示例
*/5 * * * *      /opt/scripts/health.sh          # 每 5 分钟
0 2 * * *        /opt/scripts/backup.sh          # 每天凌晨 2 点
0 3 * * 1        /opt/scripts/clean.sh           # 每周一 3 点
0 0 1 * *        /opt/scripts/report.sh          # 每月 1 号
30 8 * * 1-5     /opt/scripts/workday.sh         # 工作日早 8:30
0 */2 * * *      /opt/scripts/sync.sh            # 每两小时

二、系统级 /etc/crontab 与 cron.d

区别于用户 crontab,系统任务多一个用户字段:

# 文件: /etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin    # ← 可以定义 PATH!

# 分 时 日 月 周 用户 命令
0 2 * * * root /opt/scripts/backup.sh

软件包和运维脚本推荐放 /etc/cron.d/xxx(一个服务一个文件,便于管理与卸载):

cat > /etc/cron.d/backup <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
0 2 * * * root /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
EOF
chmod 644 /etc/cron.d/backup     # 权限必须是 644, 否则被忽略

三、两大经典陷阱

陷阱一:PATH 不一致

cron 执行环境不是登录 shell,PATH 通常只有 /usr/bin:/bin。所以你手动跑得好好的脚本,cron 里报 command not found。

解法(任选其一):

# 1. 脚本里全部用绝对路径
/usr/local/bin/rsync -a ...

# 2. 脚本开头自己定义 PATH
export PATH=/usr/local/bin:/usr/bin:/bin:$PATH

# 3. crontab 文件顶部写 PATH=...

陷阱二:% 是特殊字符

cron 里 % 会被解释成换行,date +%F 直接废掉:

# ❌ 错误
0 2 * * * /opt/backup.sh > /backup-$(date +%F).log

# ✅ 正确: % 用反斜杠转义
0 2 * * * /opt/backup.sh > /backup-$(date +\%F).log

排查方法

# 看 cron 是否执行过你的任务
grep CRON /var/log/cron | tail

# 手动模拟 cron 环境
env -i /bin/sh -c '/opt/scripts/backup.sh'    # 干净环境跑一遍

四、任务必须"幂等 + 有日志"

好的定时任务脚本三要素:

#!/usr/bin/env bash
set -euo pipefail

LOG=/var/log/backup.log
exec >>"$LOG" 2>&1              # 所有输出进日志
echo "=== $(date '+%F %T') 开始备份 ==="

# 防重复执行: 拿不到锁就退出
exec 9>/var/run/backup.lock
flock -n 9 || { echo "上一次任务还没跑完, 跳过"; exit 0; }

# ... 备份逻辑
echo "=== 备份完成 ==="

五、常用模板

每日数据库备份(保留 7 天):

0 2 * * * root mysqldump --single-transaction mydb | gzip > /backup/mydb-$(date +\%F).sql.gz && find /backup -name "mydb-*.sql.gz" -mtime +7 -delete

日志切割(其实更推荐 logrotate,这里给个 cron 思路):

0 0 * * * root find /var/log/myapp -name "*.log" -size +100M -exec gzip {} \;

证书到期监控(剩余 30 天告警):

0 9 * * 1 root for d in /etc/nginx/certs/*.crt; do end=$(openssl x509 -enddate -noout -in "$d" | cut -d= -f2); left=$(( ($(date -d "$end" +%s) - $(date +%s)) / 86400 )); [ "$left" -lt 30 ] && echo "$d 将在 ${left} 天后过期"; done

六、anacron 与 systemd timer

特性 cron anacron systemd timer
粒度 分钟 天 秒级
错过的任务 跳过 开机补跑 可配置
依赖管理 无 无 可依赖服务/网络
日志 /var/log/cron 自带 journalctl -u xx.timer

服务器 7×24 在线用 cron;笔记本/会关机的机器用 anacron;现代发行版中,logrotate、man-db 等已迁到 systemd timer:

systemctl list-timers | head
journalctl -u myapp.timer     # 看 timer 日志

小结

  • 记住五段表达式 + */n 步进即可覆盖 90% 场景
  • 绝对路径、转义 %、输出重定向到日志是保命三件套
  • grep CRON /var/log/cron 是排查第一现场
  • 系统级任务放 /etc/cron.d/,一个场景一个文件

本文是「Linux 运维」系列第 4 篇。