很多人刚接触 Linux 系统时都会遇到一个困惑想定时跑个脚本身边人一会儿说用at一会儿又说用crontab打开配置文件一看五颗星加一串命令脑子直接浆糊。其实这两个命令是互补的一个管“一次性”一个管“周期性”搞懂区分逻辑再上手一点都不难。这篇东西我按实际运维中会遇到的思路来写先讲清楚两个工具的定位差异再分别把at和crontab的关键参数、配置方式、日志排错讲透最后给出一套完整的定时任务落地建议。无论你是刚入手 Linux 的开发者还是被服务器上各种定时脚本折腾过的运维都应该能从里面找到可以直接用的东西。1. 两个命令解决的是两件事别混着用先说一个很多教程没讲透的点at和crontab虽然都叫定时任务但它们的业务场景完全不同。搞混了会出现一种很尴尬的情况——你明明用at提交了一个每天凌晨三点要执行的任务结果第二天发现它只跑了一次就再无下文因为at本来就不管重复执行。at处理的是一次性任务。比如你想今晚 23:30 给数据库做一次临时备份或者两个小时后发一封测试邮件这种“只执行一次、到点就完事”的需求就是at的本职工作。它的名字本身就很直白英文里 at 表示时间点它强调的是“在某个具体时刻做这件事”。crontab处理的是周期性任务。它来自 cron 这个单词跟“时间表”一个意思。你想让系统每天凌晨备份、每周日凌晨清理日志、每分钟检查一次服务存活状态这些需要按秒级、分级、天级、周级不断重复的任务只能用crontab来定义。它包含六个字段前五个字段组合出一个周期性的时间表达式最后一个字段是具体要执行的命令。有一个更直观的方法帮你记忆这两个工具的分工把服务器想象成一个值班室。at相当于在值班日志上写一条“今晚八点去机房巡检”只提醒一次crontab相当于写进值班制度里的“每天八点准时巡检”只要制度不改它就一直在。知道了两者的边界再来看具体操作。命令本身非常简单但真正容易翻车的是一些隐藏的权限、路径、日志细节。下面我拆开讲。2. at 命令的核心实操与避坑要点2.1 基本语法与常用写法at命令的用法很简单at [选项] 时间先输入命令指定时间回车后会进入一个交互式输入界面你输入要执行的命令序列最后按Ctrl D提交。也可以直接配合echo或是用管道直接把命令传给它这样更适合在脚本里使用。echo mysqldump -u root test_db /tmp/backup.sql | at 23:30时间部分支持几种很贴近人类表达习惯的写法不用计算复杂的日期格式at 23:30今天 23:30如果时间已经过了则顺延到明天at 4:00 2026-01-20指定具体日期at now 5 minutes5 分钟后执行at noon、at teatime下午茶时间 16:00、at midnight午夜 00:00at 9:00 AM tomorrow明天早上九点这里有一个我刚上手时容易搞错的地方at的时间是执行命令的瞬间而系统里如果开了多用户环境at作业执行时不会加载你的交互式 shell 环境。也就是说echo $HOME这类变量和你手动在终端里敲出来的可能完全不一致所以脚本想依赖环境变量的话要么写绝对路径要么在命令前先source /etc/profile。2.2 管理已经提交的任务提交之后发现写错了或者想看看当前有哪些任务在排队用这几个命令atq # 列出当前用户的at任务列表 atrm 5 # 删除编号为5的at任务atq输出的编号就是任务的 IDatrm后面跟编号就能删掉对应任务。要注意默认情况下管理员可查看和删除所有用户的任务但普通用户只能看自己的。任务的输出默认会通过邮件发给提交任务的用户邮件这个功能在很多精简环境里没有配置于是输出就静默丢失了。这个坑非常典型第一次用at提交任务的初学者经常发现自己脚本跑完没有任何反馈还以为是没执行。可以用重定向把输出写到文件里echo date /tmp/at_test.log 21 | at now 1 minute比如上面这个任务就把执行结果统一追加到/tmp/at_test.log里方便事后去查。2.3 权限控制的细节at的权限控制不是看用户组而是看两个文件/etc/at.allow白名单文件里列出允许使用at的用户一行一个。/etc/at.deny黑名单阻止某些用户使用at。这两个文件的优先级是如果at.allow存在则只有里面的用户能使用atat.deny直接被忽略如果at.allow不存在才看at.deny不在黑名单里的用户都能用如果两个文件都不存在仅 root 能使用at。几个系统对这个文件的默认处理可能不同但逻辑都是一致的。运营团队共用服务器的时候建议显式创建at.allow文件避免每个人都随意提交任务省得排查问题时分不清是谁的锅。3. crontab 的核心实操与时间表达式解析3.1 五分钟理解五颗星的时间表达式crontab的配置格式可以简化为6个字段但实际上常见写法是5个时间字段加1个命令字段。为了好记忆可以用这句话来记“分 时 日 月 周”。每颗星依次对应的是命令会在被标记的任意时刻执行。整个格式长这样分 时 日 月 周 要执行的命令举几个最常用的例子30 3 * * * /usr/bin/backup.sh # 每天凌晨3点30分执行 */5 * * * * /usr/bin/check.sh # 每5分钟执行一次 0 2 * * 1 /usr/bin/cleanup.sh # 每周一凌晨2点执行 0 4 1 * * /usr/bin/report.sh # 每月1号凌晨4点执行 reboot /usr/bin/start_service.sh # 系统重启后执行初次上手时最容易犯的错误是把时间顺序反了。有人会下意识把30 3当成 “3点30分”这没错但一写成任务就翻车比如要写“每3小时执行一次”居然写成0 */3 * * *然后漏了最前面的分字段只写*/3 * * * *变成每3分钟跑一次等着服务器被自己的脚本打崩。注意*的含义是“每一个”*/是“每隔多少”的意思。*/5在分字段里表示每5分钟一次在时字段里表示每5小时一次位置不同含义不同。crontab里的周和日的优先级也比较特殊当“日”和“周”同时设定时两个条件满足任意一个命令就会执行。如果写0 0 15 * 5表示“每月15号且同时是周五”严格说实际效果是“每月15号或在周五执行”很多人研究半天为什么坏是坏在这里。3.2 配置与管理的正确姿势有两个容易混的命令crontab -e和直接编辑/etc/crontab。如果你只想管理自己用户的任务用前者如果想编辑系统级任务或者想在里面额外指定“以某个用户身份执行”一般用后者。crontab -e编辑的是当前用户的任务文件保存后立刻生效不需要重启任何服务。系统级别的任务文件格式会多一个用户字段0 3 * * * root /usr/local/bin/backup.sh这个表格的意思就是每天凌晨3点用root身份去执行/usr/local/bin/backup.sh。普通用户没有权限直接改/etc/crontab运维在管理多用户服务器时会把所有系统级任务集中放这里方便审计。查看当前用户已有任务crontab -l删除当前用户的所有任务crontab -r提示crontab -r不加任何提示直接清空没有二次确认手滑就没了。运维界有一条铁律在执行这个命令前先crontab -l看一眼内容并备份。我自己通常执行crontab -l crontab_backup.txt给自己留条后路。3.3 环境变量与路径的坑crontab执行任务时环境变量跟你手动终端登录时完全不同。它会用默认的PATH基本就是/usr/bin:/bin而你装在/usr/local/bin里的脚本命令它八成都找不到。一个典型的故障场景是你手动执行备份脚本一切正常但定时任务一跑就报mysqldump: command not found。原因很简单mysqldump装在/usr/local/mysql/bin下面cron 的 PATH 里没有这个目录。常规的解法有两种。第一种是在脚本头部显式 export 环境变量与 PATH第二种是把所有外部命令写成绝对路径。我自己的项目里基本是混着用脚本开头写成#!/bin/bash export PATH/usr/local/bin:/usr/local/mysql/bin:/usr/bin:/bin:$PATH把需要用的工具路径都加进去后续命令不用每个都写全路径代码干净又不容易踩坑。还有一个%号的问题。crontab里百分号是有特殊含义的在命令里出现%会被当作输入换行符传给命令的时候直接截断。如果你要在脚本里用date %Y%m%d这种写法必须转义0 2 * * * /usr/bin/backup.sh /tmp/backup_$(date \%Y\%m\%d).log 21这里我把整条命令塞进了一个外层带转义的形式里实际处理时注意每个%都要加反斜杠。4. 实战组合 at 与 crontab 构建一套可靠的定时运维体系4.1 日常备份的定时策略设计一个典型的项目需要做数据库每日备份、日志清理、临时任务提交三个层面的事。我用一个完全虚构但很有代表性的模拟项目 X 来说明整套配置长什么样。每日备份用crontab处理0 1 * * * /opt/script/backup_db.sh /var/log/backup_db.log 21 30 1 * * * find /data/backup -mtime 7 -name *.sql.gz -exec rm -f {} \;第一条每天凌晨1点整执行数据库备份脚本第二条在备份完成后30分钟清理7天以前的备份压缩包避免磁盘被撑爆。日志和数据文件都重定向到了明确的路径排错时直接去翻文件不必去系统邮件目录里找。某些临时性的运维动作比如今晚2点半要升级某个模块但升级前希望先做一个独立快照这时直接用at更合适echo /opt/script/snapshot.sh | at 2:30 2026-01-20这个任务只执行一次不污染crontab里的规矩模板又能在指定时间自动触发。可以说crontab管日常、at管临时这两个工具配合使用才能覆盖一个运维场景里所有的定时需求。4.2 防止脚本重复运行定时任务跑多了会碰到一种很典型的故障上一个周期脚本还没结束下一个周期的又启动了两边同时在操作同一批文件直接数据异常。比如备份脚本执行时长超过5分钟但你的crontab是每5分钟执行一次就会发生重叠。可靠的解决办法是在脚本里加锁。我用的是最简单也最实用的文件锁方案#!/bin/bash LOCK_FILE/tmp/backup.lock exec 9$LOCK_FILE if ! flock -n 9; then echo Another backup process is running, exit. exit 1 fi # 正式任务逻辑 /usr/bin/mysqldump ...flock是系统自带的锁命令-n参数表示非阻塞如果拿不到锁就直接退出。这个方案比pidof判断进程号更稳因为哪怕脚本崩了锁文件还在老进程没结束前新的永远进不来。4.3 日志集中与轮转任务一多日志管理就成了问题。我给每个任务单独建了日志目录并用logrotate做日志轮转避免单个日志文件无限膨胀。/etc/logrotate.d/project配置示例/var/log/project/*.log { daily rotate 30 compress missingok notifempty }意思就是/var/log/project目录下的所有日志每天轮转一次保留最近30份旧日志自动压缩。配置完成后定期手动执行logrotate -f验证是否生效不要真的等一个月后再去检查那时日志可能已经几十 GB 了。5. 常见故障速查与排错指南定时任务不怕报错怕的是报错信息去了你找不见的地方。以下是我实操中遇到频率最高的几类问题整理成表方便直接对着查问题现象可能原因排查方法任务没执行但没有错误输出cron服务没有启动systemctl status crond查看服务状态服务挂了自然啥都不跑脚本执行了但命令找不到cron 的 PATH 不完整在脚本头部 export PATH或命令写绝对路径任务每天重复执行多次时间表达式写反重新核对分、时、日、月、周的顺序输出日志是空的输出未重定向被系统邮件吞了显式加 /var/log/xxx.log 21at提交后系统报错时间格式不受支持用now 1 minutes这类相对时间最稳多个脚本互相覆盖同一文件无锁机制任务重叠运行在脚本内加flock互斥锁任务时区不对系统时区不是本地时区timedatectl status查看必要时改/etc/localtime日志排查也是个重要技能。大部分 Linux 发行版cron的执行记录会写在/var/log/cron里。每次任务执行都会留下一条记录包括执行时间、执行用户和具体命令。前端看不到的问题去日志里一翻就现原形。如果配置了系统日志也可以直接查journalctl -u crond。我自己排查定时任务故障的固定流程是先查 cron 服务状态再查 /var/log/cron 日志最后手动复制命令行执行一遍。这三步能解决九成问题。如果三步都走完仍然没头绪再考虑脚本内部逻辑问题比如权限不足、磁盘满了、锁没有释放。举个例子某次我维护的项目连续三天凌晨没有生成备份文件。按流程第一步服务状态正常第二步去/var/log/cron看到任务“确实执行了”第三步手动跑脚本本身也成功。那问题出在哪最后翻到脚本内部才发现它依赖一个环境变量而这个变量只在登录 shell 里存在。cron 启动脚本时没加载~/.bashrc自然拿不到。把环境变量定义直接挪进脚本开头立刻恢复。这种环境差异导致的诡异故障我觉得是新手最容易栽的地方所以特意建议所有写定时任务的开发者把脚本写得尽量“自包含”环境变量自己设路径写绝对依赖哪几个文件提前声明不要让脚本去猜运行环境。6. 时区、权限与多用户协作的进阶经验多用户服务器上用定时任务还有一个很容易被忽视的权限协作问题。不加限制时所有用户都能用crontab -e建自己的任务十几个人的团队就有十几个任务集合。看似互不干扰实际所有任务都写进同一个 cron 机制里一旦某个任务失控整个系统的负载都会被打高。我比较推荐的原则是普通用户尽量不使用个人 crontab而是把需要周期执行的任务统一收敛到专门的运维用户或使用系统级/etc/crontab统一管理并在每行注明任务用途与负责人。集中化管理的优势在排查问题时最直观——打开一个文件就能看到全貌而不是挨个用户crontab -l。at命令的权限控制相对简单只要你把普通用户排除在at.allow之外他们就不能提交一次性任务。这在生产环境里是合理限制避免某位同事随手提交个“现在 2 分钟重启服务”的任务半夜把整个业务弄断。时区问题容易被忽视但极其致命。如果服务器时间不同步比如一台机器时区是 UTC 而应用时区是东八区配置里写的凌晨2点实际是本地时间上午10点。数据库备份从凌晨2点变成上午10点刚好撞上业务高峰磁盘 IO 被打满接口立刻变慢。检查时区最直接的方式timedatectl输出里的Time zone字段就是当前时区。如果不对直接改成需要的时区timedatectl set-timezone Asia/Shanghai改完时区以后再测试定时任务确保时间符合预期。比这更深一层是时间同步问题服务器如果没配 NTP时间漂移几十分钟也是常有的事定时任务全部不准。这一步在很多系统镜像里是默认配好的但自己搭的环境一定要检查一下。7. 最后再给几条实在建议在我维护过的所有项目里定时任务从来不是“写完就行”的东西它需要整体设计包括日志怎么收、失败怎么告警、脚本之间怎么避免冲突。三个经验最后分享一下。第一所有任务输出必须落盘。不让 cron 把输出发到邮箱而是每条任务都显式指定 /var/log/xxx.log 21。落盘之后可以配合logrotate做轮转出问题时翻日志效率高得多。第二脚本加锁比加判断好。进程重叠是线上事故高发点用flock或简单的文件锁在脚本入口挡住并发执行比脚本内部用临时变量判断慎靠谱得多因为锁文件天然是崩溃安全的。第三修改配置前先备份改完立即验证。crontab -l先看现状改动之后用crontab -l确认新配置确实写入再用at now 1 minute或者把时间临时改成下一分钟验证一次。生产环境里不是每次都有机会等一个周期跑完再确认能提前验证就别干等。定时任务的问题往往不是出在格式上而是出在环境、时长、权限这些软性因素里。把这两条命令吃透配合一套自己的排错流程基本就能覆盖日常运维中九成以上的定时调度需求。