这个命令是干啥的
crontab 是Linux上用来搞定时任务的。我最早接触它是在帮朋友维护一个老项目,每天凌晨需要跑一个数据同步脚本。那时候不太熟,写完任务发现第二天数据没过来,排查了一上午才发现是环境变量的问题。
crontab的用处其实就一个:让系统在指定时间自动执行某个命令或脚本。日志清理、数据备份、定时巡检、报表生成,这些重复性的工作全都可以交给它。
每个用户都有自己的crontab配置文件,互相不干扰。root用户的crontab可以做系统级任务。
基本用法(3分钟上手)
编辑定时任务
# 编辑当前用户的定时任务列表
crontab -e
第一次执行会让你选编辑器,我一般选vim。进去后格式就是一行一个任务:
# 分 时 日 月 周 要执行的命令
* * * * * command
五个时间字段依次是:分钟(0-59)、小时(0-23)、日(1-31)、月(1-12)、周(0-7,0和7都表示周日)。
几个例子:
# 每天早上8点执行备份脚本
0 8 * * * /home/ubuntu/backup.sh
# 每5分钟检查一次服务是否存活
*/5 * * * * /home/ubuntu/check_service.sh
# 每周一凌晨3点清理日志
0 3 * * 1 /home/ubuntu/clean_logs.sh
# 每天中午12点和下午6点各执行一次
0 12,18 * * * /home/ubuntu/sync.sh
# 每月的1号和15号凌晨2点执行
0 2 1,15 * * /home/ubuntu/monthly_report.sh
查看现有任务
# 列出当前用户的所有定时任务
crontab -l
清空所有任务
# 删除当前用户的所有crontab任务
crontab -r
这个命令用完就没法恢复了,我一般只敢在确定没有任务的时候用。
其他用户(需要root)
# 以root身份查看www用户的定时任务
crontab -u www -l
# 编辑www用户的定时任务
crontab -u www -e
进阶骚操作
秒级任务怎么办?
crontab最小粒度是分钟,做不了秒级。但有时候我真的需要每10秒检查一次队列状态。怎么搞?
方法一:shell里写循环
# crontab里每1分钟执行,脚本内部sleep循环(不推荐,容易堆积进程)
* * * * * /home/ubuntu/every_10s.sh
脚本内容:
#!/bin/bash
# 每隔10秒执行一次,总共执行6次(1分钟)
for i in $(seq 1 6); do
# 你的实际任务放这
/usr/bin/curl -s http://localhost/health > /dev/null
sleep 10
done
方法二:用systemd timer(后面会讲)
环境变量问题(重点坑)
crontab执行时,PATH环境变量只有 /usr/bin:/bin 这些基础路径。你自己装到 /usr/local/bin 或者自定义目录下的命令,直接写命令名是找不到的。
我的解决方案有三种:
方案一:脚本里写全路径
# 所有命令都写绝对路径,最稳妥
0 3 * * * /usr/local/bin/python3 /home/ubuntu/scripts/backup.py
方案二:在crontab开头设环境变量
# 在crontab -e编辑器最前面加上
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
# 这样后面所有任务都能用自定义路径的命令
0 3 * * * python3 /home/ubuntu/scripts/backup.py
方案三:脚本开头source用户环境变量
#!/bin/bash
# 加载用户的环境变量文件
source ~/.bashrc
cd /home/ubuntu/app
python3 backup.py
输出重定向
crontab任务的输出默认会通过邮件发给当前用户(如果系统配了mail)。大部分服务器上根本没配mail服务,输出就丢了,出问题都不知道。
# 标准做法:把输出追加到日志文件
0 3 * * * /home/ubuntu/backup.sh >> /var/log/backup.log 2>&1
# 如果不想保留日志,直接扔黑洞
0 3 * * * /home/ubuntu/backup.sh > /dev/null 2>&1
跟systemd timer对比
crontab方便但不够灵活,systemd timer可以替代它:
| 特性 | crontab | systemd timer |
|---|
| 配置复杂度 | 一行搞定 | 要写两个文件(service + timer) |
| 秒级任务 | 不支持 | 支持精度到纳秒 |
| 依赖控制 | 无 | 可以等网络/磁盘就绪再跑 |
| 失败重试 | 要自己写 | 系统自带Restar= |
| 日志记录 | 要自己重定向 | 自动journalctl |
有次我需要"等docker启动之后再跑的定时任务",crontab搞不定依赖,换成systemd timer + After=docker.service就好了。
禁用邮件通知
# 在命令末尾加上MAILTO=""或>/dev/null
MAILTO=""
0 3 * * * /home/ubuntu/backup.sh
避坑指南
坑1:crontab执行了但脚本没生效
我有段时间写了个备份脚本,确认crontab日志显示执行了,但备份文件没生成。最后发现脚本里用了相对路径 ./backup/,而crontab的工作目录是用户的home目录,不是脚本所在目录。
解决方法:脚本里所有路径都写绝对路径,或者在脚本开头加 cd $(dirname $0)。
坑2:环境变量不是你以为的那个
切到Python虚拟环境时特别容易翻车。你在终端里激活了虚拟环境能跑,但crontab不会加载虚拟环境。
# 错误:直接写python,crontab找不到虚拟环境的python
0 3 * * * python /home/ubuntu/myapp/run.py
# 正确:用虚拟环境的python绝对路径
0 3 * * * /home/ubuntu/myapp/venv/bin/python /home/ubuntu/myapp/run.py
坑3:%号需要转义
crontab里 % 有特殊含义,表示换行。如果你的命令里有日期格式 $(date +%Y%m%d),%号要转义:
# 错误的写法:%会被crontab吃掉
0 3 * * * /home/ubuntu/backup.sh --date=$(date +%Y%m%d)
# 正确写法:百分号前面加反斜杠
0 3 * * * /home/ubuntu/backup.sh --date=$(date +\%Y\%m\%d)
更稳妥的做法是把这类代码放到脚本里,不要在crontab行里写复杂表达式。
坑4:脚本执行权限
crontab执行脚本需要脚本本身有执行权限。我见过有人写好了脚本但没chmod +x,crontab跑的时候报Permission denied。
# 别忘了给执行权限
chmod +x /home/ubuntu/backup.sh
实战场景(重点!结合真实运维场景)
场景1:日志清理(真实案例)
我管理的一台应用服务器,日志增长很快,一个服务每天产生500MB日志。如果不清理,10天就把磁盘撑爆了。
# 每天凌晨3点执行日志清理脚本
0 3 * * * /opt/scripts/clean_logs.sh >> /var/log/clean_cron.log 2>&1
脚本内容:
#!/bin/bash
# 日志保留天数
DAYS=7
# 日志目录
LOG_DIR="/var/log/myapp"
# 删除7天前的日志文件
find $LOG_DIR -name "*.log.*" -mtime +$DAYS -exec rm -f {} \;
# 压缩3天前的日志(节省空间)
find $LOG_DIR -name "*.log" -mtime +3 -exec gzip {} \;
# 记录清理结果
echo "$(date): 清理完成,剩余空间: $(df -h / | tail -1 | awk '{print $4}')"
场景2:服务健康检查并自动恢复
有次线上服务老挂,但又不频繁,人盯不过来。我写了这么个定时任务:
# 每5分钟检查一次服务健康
*/5 * * * * /opt/scripts/health_check.sh >> /var/log/health.log 2>&1
脚本:
#!/bin/bash
# 检查应用端口是否存活
SERVICE="myapp"
PORT=8080
# 用curl测试端口是否正常响应
curl -f -s http://localhost:$PORT/health > /dev/null 2>&1
# 根据返回值判断服务是否正常
if [ $? -ne 0 ]; then
echo "$(date): $SERVICE 挂了,尝试重启"
# 用systemctl尝试重启服务
systemctl restart $SERVICE
# 记录重启结果
if [ $? -eq 0 ]; then
echo "$(date): $SERVICE 重启成功"
else
echo "$(date): $SERVICE 重启失败,请人工介入"
fi
fi
场景3:crontab不执行的排查流程
这是我最常用的排查思路:
1. 确认crontab任务存在:crontab -l
2. 看cron服务是否在跑:systemctl status crond(CentOS)或 systemctl status cron(Ubuntu)
3. 看系统日志找线索:grep CRON /var/log/syslog 或 journalctl -u cron
4. 手动运行脚本看是否正常:bash /path/to/script.sh
5. 检查脚本是否有执行权限:ls -l /path/to/script.sh
6. 检查脚本里是不是用了相对路径
7. 检查环境变量(PATH、虚拟环境等)
8. 检查输出重定向有没有写对
这个流程我背得滚瓜烂熟,因为踩坑太多次了。
场景4:一次性延迟任务
crontab不支持"10分钟后执行一次"这种场景。这时候我一般用 at 命令:
# 10分钟后执行
echo "/opt/scripts/backup.sh" | at now + 10 minutes
或者用 sleep:
# 当前时间 + 延迟
sleep 600 && /opt/scripts/backup.sh
今日作业
写一个crontab定时任务,实现以下需求:
1. 每天凌晨2:30清理 /var/log/myapp/ 目录下超过3天的 .log 文件
2. 输出结果重定向到 /var/log/myapp/clean.log,错误输出也要记录进去
3. 每周一早上9:00生成一份周报(假设周报脚本在 /opt/report/weekly.sh)
4. 每10分钟用 curl 检查本机 http://localhost:3000/health 是否正常,如果返回非200状态码,记录到 /var/log/myapp/health.log
请写出完整的crontab配置内容。