你有没有遇到过这种场景:临时有个任务要在某个固定时间跑一次,比如半夜两点执行个数据库备份、五分钟后重启某个服务、或者等空闲时跑一个耗资源的脚本。大多数人第一反应是去写crontab,但crontab的定位是周期性任务,你写进去还得手动清理那条记录,否则每周都会再触发一次,反而埋了个雷。在Linux CentOS7这类系统上,其实有一个被低估的原生命令专门解决这种“一次性定时”需求,那就是at。说白了,cron管的是“每隔多久”,at管的是“到点就执行,执行完就忘”。
这篇文章就围绕CentOS7环境下的at命令展开,从服务安装、时间语法、任务管理、常见坑点,到和cron、systemd timer的对比选型,全流程给你捋一遍。不管你是运维新手还是写脚本的老手,只要需要处理一次性定时任务,这篇文章都能给你一个直接能用的操作方案,顺便帮你避开那些文档里不会写的坑。
1. at定时任务的适用场景:为什么cron解决不了的需求反而要靠at
1.1 一次性任务用cron就是给自己找麻烦
很多人习惯“所有定时都用crontab”,但cron天生是周期性的。你往crontab里写一条“0 2 * * * /opt/backup.sh”,意味着每天凌晨两点都执行。如果这个任务其实只今天需要跑一次,那过了今天,这条cron就成了个定时炸弹——你不知道它哪天会被触发,可能不小心执行了重复操作,或者把旧数据覆盖了。
用at就不一样。at的任务一旦执行完,队列里就干干净净,根本不需要你记得去清理。这种“用完即走”的特性,和一次性任务完美匹配。
1.2 at最擅长的三类场景
从我实际经验看,at适合以下场景:
-
延迟执行型:比如你刚上线完代码,希望五分钟后自动重启Nginx让配置生效,但又不想坐在终端前等着,直接
at now + 5 minutes,把重启命令丢进去,到点自动执行。 -
低峰窗口型:数据库备份、日志清理、大数据量计算这些任务,白天跑会抢占业务资源,你想把它放到凌晨执行。用cron的话,你得保证这个脚本只在今天跑,万一忘了删,明天又跑一次。用at就能做到“今晚跑一次,明天队列为空”。
-
资源条件触发型:at家族里的batch命令,可以在系统平均负载低于某个阈值时才执行任务。这种“负载低了再干活”的需求,cron根本做不到。
1.3 怎么判断该用at还是cron
拿到一个需求,先问自己两个问题:
- 这个任务是每天/每周/每月固定重复的吗?是 → 用cron。
- 这个任务只是某个特定时刻执行一次,或者“从现在起过了N分钟执行一次”?是 → 用at。
如果任务是“平时不用,偶尔心血来潮跑一次”,那at绝对是首选。判断标准就这么简单,别把简单问题复杂化。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. CentOS7上at服务的完整启用流程:不装包不启服务全是白搭
2.1 CentOS7默认不装at,先装包
CentOS7的最小化安装通常没有at。你直接敲 at 会提示 command not found。先用yum装:
bash复制yum install -y at
安装包不大,很快就完事。装完后确认一下版本:
bash复制at -V
能输出版本号,说明命令可用了。但注意,命令可用不代表服务在跑。
2.2 启动atd服务并设置开机自启
at命令本身只是个客户端,真正到点执行任务的是后台的atd服务。CentOS7用systemd管理服务,所以操作是:
bash复制systemctl start atd
systemctl enable atd
systemctl status atd
第一条启动服务,第二条设置开机自启,第三条确认状态。正常情况下你会看到 active (running)。如果看到的是inactive或failed,多半是端口被占或者安装有问题,查一下日志再说。
提示:有的教程会写
service atd start,这在CentOS7上也兼容,但建议用systemctl,毕竟7之后的系统都以systemd为准。
2.3 权限控制:at.allow和at.deny怎么用
at服务有一个很容易被忽略的权限体系:/etc/at.allow 和 /etc/at.deny。
- 如果存在
/etc/at.allow,那只有这个文件里列出的用户可以使用at。 - 如果
/etc/at.allow不存在,但/etc/at.deny存在,那不在 at.deny 里的用户都能用at。 - 如果两个文件都不存在,只有root能用at(具体行为取决于发行版,CentOS7默认情况是只有root能用,除非有at.deny)。
这个机制其实和 /etc/cron.allow、/etc/cron.deny 一模一样。我曾经在机器上给开发账号开了sudo权限,结果发现开发自己创建的at任务经常莫名失败,排查半天才发现是 at.deny 里默认列出了哪些用户。所以如果你的任务创建时报错 You do not have permission to use at.,十有八九是权限控制的问题。
检查方法:
bash复制cat /etc/at.allow
cat /etc/at.deny
如果不想启用限制,删掉两个文件,或者让它们为空即可。生产环境建议保留,只给该用的账号开权限。
3. at命令的使用细节:从时间语法到任务入队,一次讲透
3.1 交互式用法:at + 时间,然后输入命令
最基础的用法是:
bash复制at 14:30
回车后,at会进入交互式提示符:
bash复制at> /opt/scripts/backup.sh
at> <EOT>
在 at> 后面输入要执行的命令,按回车换行,可以连续输多条。输入完成后按 Ctrl+D 提交任务。这里的 <EOT> 是Ctrl+D的显示效果,表示输入结束。任务就会被交给atd,等到点执行。
这种交互方式适合临时手动输入一两条命令。但如果你要批量、一次性、可复用地提交任务,我更推荐下面的非交互式写法。
3.2 时间语法全解:这才是at的核心功力
at的时间语法非常灵活,很多人用它用得少,就是被时间格式吓到了。其实总结下来就几类。
绝对时间:
bash复制at 23:59
at 23:59 2025-03-20
at 10:30 tomorrow
at 22:00 friday
at 12:00 noon
at 18:00 teatime
at 24:00 midnight
相对时间:
bash复制at now + 5 minutes
at now + 2 hours
at now + 1 day
at now + 3 weeks
这里的时间单位支持:minutes、hours、days、weeks,也可以用单数形式minute、hour、day、week。现在时间点可以用 now,比如“五分钟之后”就直接写 now + 5 minutes。
日期格式:
MMDDhhmm格式,比如 03202025 表示2025年3月20日20点25分?不对,其实格式是MMDDhhmm后面可以接年份,比如032014002025,这种写法容易出错,不建议日常用。YYYY-MM-DD hh:mm这种比较直观:at 23:59 2025-03-20。- 也支持
HH:MM YYYY-MM-DD,但建议统一用一种,避免混用导致理解错误。
特殊时间:
midnight= 00:00noon= 12:00teatime= 16:00(没错,at命令里把下午四点叫茶歇时间)today、tomorrow可以直接跟在时间后面。
注意:如果你写
at now + 5 minutes,这里的now不能去掉,写成at + 5 minutes会报语法错误。这是新手容易犯的第一个错。
3.3 非交互式写法:一条命令提交整个任务
实际运维中,我更习惯把命令通过管道或heredoc传给at,这样脚本化、自动化都方便。典型的非交互式写法:
bash复制echo '/opt/scripts/backup.sh' | at now + 10 minutes
或者一次传多行命令:
bash复制at now + 1 day <<EOF
/opt/scripts/cleanup.sh
echo "cleanup done" >> /var/log/cleanup.log
EOF
这种写法好处是:你可以把整个at任务写进shell脚本里,想什么时候提交都行。比如你写了一个发布脚本,发布完成顺手 echo "systemctl restart nginx" | at now + 5 minutes,完美实现延迟重启。
3.4 batch命令:负载低于阈值才执行的“佛系定时”
at命令族里还有个兄弟叫 batch。batch不需要指定具体时间,它只负责把任务放进队列,等系统平均负载降到0.8以下时自动执行。用法:
bash复制echo 'make -j4' | batch
然后任务就排在那里,系统空闲了才跑。这在执行一些CPU密集型任务时很有用,避免和白天的业务高峰抢资源。如果你觉得0.8这个阈值不合适,可以通过 atd 启动参数 -l 来调整,比如设置成 -l 2.0。在CentOS7上修改atd的启动参数,一般用systemd override方式,这里先不展开,知道有这回事就行。
4. at任务的管理与排查:atq、atrm、日志与经典坑点
4.1 用atq查询任务队列,atrm删除任务
创建了at任务之后,怎么查看?用 atq 或 at -l:
bash复制atq
输出示例:
code复制5 2025-03-20 23:59 a root
6 2025-03-21 02:00 a root
第一列是任务号,第二列是执行时间,第三列是队列,第四列是用户。想删除某个任务:
bash复制atrm 5
或者 at -d 5,效果一样。删除后atq再看一眼,确认没了。这个操作很重要——如果发现提交错了任务,及时删掉,别等它到点执行了再后悔。
4.2 输出重定向:别把输出丢进邮件黑洞
at任务默认会把执行结果通过邮件发给当前用户。但很多服务器根本没配邮件服务,邮件发不出去,任务输出就丢了。更常见的是,你压根不想看邮件,只想把日志写到文件里。
所以正确的姿势是,在at任务中主动重定向输出。比如:
bash复制at now + 5 minutes <<EOF
/opt/scripts/backup.sh > /var/log/backup_at.log 2>&1
EOF
这样stdout和stderr都会被写到日志文件里,排查时直接看日志即可。别忘了加上 2>&1,不然错误信息你还是看不到。
4.3 坑:环境变量和PATH的问题
这是at任务最容易踩的坑,没有之一。
at任务执行时,使用的是atd的默认环境,不是你当前shell的环境。所以如果你的命令里用了某个安装到 /usr/local/bin 的软件,at执行时可能找不到这个命令。比如:
bash复制at now + 1 minute <<EOF
python3 /opt/scripts/test.py
EOF
如果你的 python3 在 /usr/local/bin,但atd的PATH路径里没有它,任务执行时就会报 python3: command not found。
解决方式:
- 在at任务里写绝对路径:
bash复制/usr/local/bin/python3 /opt/scripts/test.py
- 或者先source一下环境变量文件:
bash复制source /etc/profile
/usr/local/bin/python3 /opt/scripts/test.py
但source /etc/profile不是万能的,有些用户自定义的变量在非交互式shell里还是加载不到。最稳妥的方法,就是用绝对路径。还有,脚本里的路径也最好全部写绝对路径,避免相对路径产生的歧义。
4.4 坑:命令中的特殊字符和heredoc转义
在at的heredoc传参里,如果命令含有 $ 符号、反引号、通配符等,要注意转义问题。
举个例子:
bash复制at now + 1 minute <<EOF
echo "PID: $$"
ls /tmp/*.log
EOF
这里 $$ 会被当前shell展开成当前shell的PID,而不是at任务执行时的PID。通配符 *.log 也会被提前展开。如果你希望等任务执行时再展开,需要转义:
bash复制at now + 1 minute <<'EOF'
echo "PID: $$"
ls /tmp/*.log
EOF
在heredoc的分隔符上加引号,或者写成 \\$、\\*,具体看你的场景。这个坑很隐蔽,我见过不少脚本在at里出现了变量被提前替换的问题。
4.5 坑:任务执行用户与权限问题
当前用户创建的at任务,默认就用当前用户权限执行。如果你本身是普通用户,at任务里就不能执行需要root权限的命令。也许你以为sudo会生效,但at的非交互环境里sudo往往要输入密码,所以任务会卡住或失败。
一个靠谱的做法是:把创建at任务和执行用户解耦。如果任务需要root权限,就用root登录后提交at任务;如果任务需要特定账号权限,就用那个账号提交。
查看at任务属于哪个用户,用 atq 最后一列可以看出来。如果任务突然失败,排除一下是不是用户权限问题。
4.6 日志排查:找不到at任务执行记录怎么办
atd的执行日志默认写到 /var/log/cron(因为at由cron服务统一记录)或者通过journalctl看:
bash复制journalctl -u atd
如果你发现at任务没执行,先查这个日志。常见错误是:
PAM authentication failed→ 用户权限或PAM配置问题。execvp(...) failed→ 命令不存在或路径错误。Permission denied→ 脚本没有执行权限,或者访问目录权限不够。
看到这些错误再对症下药,比瞎改快得多。
5. at与crontab、systemd timer的选型对比:别再用错工具
5.1 三者的定位差异
很多时候我们遇到的不是“会不会用at”,而是“该用at还是crontab还是systemd timer”。我把三者放在一张表里对比,你一眼就能看出来:
| 对比项 | at | crontab | systemd timer |
|---|---|---|---|
| 任务类型 | 一次性 | 周期性 | 周期性/一次性(需手动配置) |
| 时间精度 | 到分钟 | 到分钟 | 可到秒 |
| 状态管理 | 简单队列 | 静态文件 | systemd unit管理 |
| 日志集成 | 系统cron日志 | 系统cron日志 | journald |
| 依赖管理 | 无 | 无 | 可设依赖、条件 |
| 适合场景 | 临时任务、延迟执行 | 每天/每周/每月固定任务 | 复杂的触发器、精确时间、依赖管理 |
从表格能看出,at的绝对优势就是“一次性的临时任务”。你让我五分钟后重启服务,用cron写要写 now 吗?不,cron没有“相对时间”的概念,你只能算好绝对时刻,写进去再等它的日期格式。而at直接 now + 5 minutes,爽快得多。
5.2 到底怎么选:我的建议
选型其实就一句话:
- 任务要重复执行 → crontab 或 systemd timer。
- 任务只执行一次 → at。
- 任务要满足某个条件才执行,比如依赖某个服务启动成功 → systemd timer。
- 任务要在系统空闲时执行 → batch(at的兄弟命令)。
这个选择规则我用了很多年,基本没出过错。别把at用来做周期任务,也别把cron用来做一次性任务,工具用对了,运维省心一半。
5.3 实战组合:让at和cron配合工作
at不一定非要和cron二选一,它们可以配合。举个例子:
你希望每天凌晨2点执行备份,但备份耗时不定,备份完成后5分钟要执行一个清理脚本。这个场景可以这样设计:
- crontab里写一条:
0 2 * * * /opt/scripts/backup.sh - backup.sh内部,在备份完成后执行:
bash复制echo "/opt/scripts/cleanup.sh" | at now + 5 minutes
这样当天的清理任务只在备份完成后才创建,清完即走。比写成 0 5 * * * 这种硬编码时间可靠得多,因为备份可能提前或延后完成。
这种“crontab定周期 + at定偏移”的组合,在运维里非常实用。既能保证按时启动,又能保证后续动作只执行一次。
6. 实际运维案例:用at实现低峰期数据库备份与延迟部署
6.1 场景:一次真实的需求
前不久我负责的一台业务服务器,每天白天数据库负载很高,DBA提出每天晚上凌晨2点做一次数据库备份,备份文件保留7天。同时,当晚如果有临时表重建任务,需要备份完成后30分钟自动执行。
这个任务如果用crontab写,得同时写两条定时,还要考虑备份是否成功,否则就是白跑。我的方案是:用at创建当晚的备份任务,备份脚本后边再接一个at任务,30分钟后清理临时表。
6.2 脚本内容与执行思路
首先,写备份脚本 /opt/scripts/mysql_backup.sh:
bash复制#!/bin/bash
BACKUP_DIR="/data/backup"
DB_NAME="mydb"
DATE=$(date +%Y%m%d%H%M)
mysqldump -u backup_user -p'xxx' --single-transaction "$DB_NAME" | gzip > "$BACKUP_DIR/mydb_$DATE.sql.gz"
if [ $? -eq 0 ]; then
echo "backup success: mydb_$DATE.sql.gz"
# 备份成功后,延迟30分钟执行重建临时表的清理任务
echo "/opt/scripts/clean_tmp_table.sh" | at now + 30 minutes
else
echo "backup failed"
exit 1
fi
然后用at提交这个备份任务,让它在凌晨2点跑一次:
bash复制echo "/opt/scripts/mysql_backup.sh > /var/log/mysql_backup_at.log 2>&1" | at 02:00
任务创建后,用 atq 确认:
bash复制atq
这时会看到一条队列记录,执行者是root,时间就是明天凌晨2点。第二天早上去看 /var/log/mysql_backup_at.log,确认备份是否成功;再用 atq 看看,备份任务和temp清理任务是不是都执行完自动消失了。
6.3 把at任务管理纳入运维闭环
很多人用at任务不记录、不监控,导致任务没执行也无从得知。我的习惯是:
- 所有at任务提交前,先在脚本里写好日志重定向路径。
- 提交后立刻用
atq确认任务已入队。 - 在日志中记录一条“at任务已创建,执行时间为xxx”,方便回溯。
- 第二天检查日志和atq,确认队列已清空。
如果任务重要,还可以在脚本末尾加一个通知机制,比如触发一个企业微信机器人或钉钉机器人,把执行结果推出来。这个和at本身无关,但能让你第一时间发现异常。
6.4 个人实操中的几个心得
at这个命令看起来很简单,真正用得顺手还是靠经验积累。我提几个小技巧:
- 在提交at任务前,先用
date -d "tomorrow 02:00" +%c验证一下时间格式,别等到任务进了队列才发现写错时间。 - at任务里的命令和脚本路径,一律习惯性写成绝对路径。不要依赖PATH,不要依赖相对路径。
- 如果你的at任务里有交互式命令,比如
top、vim,务必重新设计,因为atd环境是非交互的,这类命令会挂起或报错。 - atq显示的时间可能和你预想的有时区差异,确认服务器时区是Asia/Shanghai再判断,别因此误判任务时间。
最后一件事,就是别滥用at。它是一次性任务的好帮手,但你如果发现自己每天都在创建at任务,那可能说明你该重新审视一下这些任务的周期性,很多重复的“一次性任务”其实应该用cron或systemd timer来管理。工具不在多,用对才行。
