在使用脚本时,你也许希望脚本能在以后某个你无法亲临现场的时候运行。Linux系统提供了多个在预选时间运行脚本的方法:at命令、cron表以及anacron。每种方法都使用不同的技术来安排脚本的运行时间和频率。接下来将依次介绍这些方法。
1.使用at命令调度作业
at命令允许指定Linux系统何时运行脚本。该命令会将作业提交到队列中,指定shell何时运行该作业。
at的守护进程atd在后台运行,在作业队列中检查待运行的作业。很多Linux发行版会在启动时运行此守护进程,但有些发行版甚至都没安装这个软件包。如果你的Linux属于后一种情况,则可以自行安装,软件包的名字如你所料,就是at。
atd守护进程会检查系统的一个特殊目录(通常位于/var/spool/at或/var/spool/cron/atjobs),从中获取at命令提交的作业。在默认情况下,atd守护进程每隔60秒检查一次这个目录。如果其中有作业,那么atd守护进程就会查看此作业的运行时间。如果时间跟当前时间一致,就运行此作业。
接下来将介绍如何用at命令提交作业以及如何管理作业。
1.at命令的格式
at命令的基本格式非常简单:
at [-f filename] time
在默认情况下,at命令会将STDIN的输入放入队列。你可以用-f选项指定用于从中读取命令(脚本文件)的文件名。
time选项指定了你希望何时运行该作业。如果指定的时间已经过去,那么at命令会在第二天的同一时刻运行指定的作业。
指定时间的方式非常灵活。at命令能识别多种时间格式。
- 标准的小时和分钟,比如10:15
- AM/PM指示符,比如10:15 PM。
- 特定的时间名称,比如now、noon、midnight或者teatime(4:00 p.m.)。
除了指定运行作业的时间,也可以通过不同的日期格式指定特定的日期。
- 标准日期,比如MMDDYY、MM/DD/YY或DD.MM.YY。
- 文本日期,比如Jul 4或Dec 25,加不加年份均可。
- 时间增量。
- Now + 25 minutes
- 10:15 PM tomorrow
- 10:15 + 7 days
提示 at命令可用的日期和时间格式有很多种,具体参见/usr/share/doc/at/timespec文件。
在使用at命令时,该作业会被提交至作业队列。作业队列保存着通过at命令提交的待处理作业。针对不同优先级,有52种作业队列。作业队列通常用小写字母a~z和大写字母A~Z来指代,A队列和a队列是两个不同的队列。
注意 在几年前,batch命令也能指定脚本的执行时间。这是个很独特的命令,因为它可以安排脚本在系统处于低负载时运行。现在,batch命令只不过是一个脚本而已(/usr/bin/batch) ,它会调用at命令将作业提交到b队列中。
作业队列的字母排序越高,此队列中的作业运行优先级就越低(谦让度更大) 。在默认情况下,at命令提交的作业会被放入a队列。如果想以较低的优先级运行作业,可以用-q选项指定其他的队列。如果相较于其他进程你希望你的作业尽可能少地占用CPU,可以将其放入z队列。
2.获取作业的输出
当在Linux系统中运行at命令时,显示器并不会关联到该作业。Linux系统反而会将提交该作业的用户email地址作为STDOUT和STDERR。任何送往STDOUT或STDERR的输出都会通过邮件系统传给该用户。
来看一个在CentOS发行版中使用at命令调度作业的例子:
bash">$ cat tryat.sh
#!/bin/bash
# Trying out the at command
#
echo "This script ran at $(date +%B%d,%T)"
echo
echo "This script is using the $SHELL shell."
echo
sleep 5
echo "This is the script's end."
#
exit
$
$ at -f tryat.sh now
warning: commands will be executed using /bin/sh
job 3 at Fri Feb 7 16:23:00 2025
$
at命令会显示分配给作业的作业号以及为作业安排的运行时间。-f选项指明使用哪个脚本文件。now指示at命令立刻执行该脚本。
使用email作为at命令的输出极不方便。at命令通过sendmail应用程序发送email。如果系统中没有安装sendmail,那就无法获得任何输出。因此在使用at命令时,最好在脚本中对STDOUT和STDERR进行重定向,如下例所示:
bash">$ cat tryatout.sh
#!/bin/bash
# Trying out the at command redirecting output
#
outfile=$HOME/scripts/tryat.out
#
echo "This script ran at $(date + %B%d,%T)" > $outfile
echo >> $outfile
echo "This script is using the $SHELL shell." >> $outfile
echo >> $outfile
sleep 5
echo "This is the script's end." >> $outfile
#
exit
$
$ at -M -f tryatout.sh now
warning: commands will be executed using /bin/sh
job 4 at Fri Feb 7 16:48:00 2025
$
$ cat $HOME/scripts/tryat.out
This scrit ran at Feb7,16:48:21This script is using the /bin/bash shell.This is the script's end.
$
如果不想在at命令中使用email或者重定向,则最好加上-M选项,以禁止作业产生的输出信息。
3.列出等待的作业
atq命令可以查看系统中有哪些作业在等待:
bash">$ at -M -f tryatout.sh teatime
warnign: commands will be executed using /bin/sh
job 5 at Sat Feb 8 16:00:00 2025
$
$ at -M -f tryatout.sh tomorrow
warning: commands will be executed using /bin/sh
jb 6 at Sat Feb 8 16:53:00 2025
$
$ at -M -f tryatout.sh 20:30
warning: commands will be executed using /bin/sh
job 7 at Fri Feb 7 20:30:00 2025
$
$ at -M -f tryatout.sh now+1hour
warning: commands will be executed using /bin/sh
job 8 at Fri Feb 7 17:54:00 2025
$
$ atq
1 Fri Feb 7 16:11:00 2025 a christine
5 Sat Feb 8 16:00:00 2025 a christine
6 Sat Feb 8 16:53:00 2025 a christine
7 Fri Feb 7 20:30:00 2025 a christine
8 Fri Feb 7 17:54:00 2025 a christine
$
作业列表中显示了作业号、系统运行该作业的日期和时间,以及该作业所在的作业队列。
4.删除作业
一旦知道了哪些作业正在作业队列中等待,就可以用atrm命令删除等待中的作业。指定要删除的作业号即可:
bash">$ atq
1 Fri Feb 7 16:11:00 2025 a christine
5 Sat Feb 8 16:00:00 2025 a christine
6 Sat Feb 8 16:53:00 2025 a christine
7 Fri Feb 7 20:30:00 2025 a christine
8 Fri Feb 7 17:54:00 2025 a christine
$
$ atrm 5
$
$ atq
1 Fri Feb 7 16:11:00 2025 a christine
6 Sat Feb 8 16:53:00 2025 a christine
7 Fri Feb 7 20:30:00 2025 a christine
8 Fri Feb 7 17:54:00 2025 a christine
$
只能删除自己提交的作业,不能删除其他人的。
2.调度需要定期运行的脚本
使用at命令安排在未来的预设时间运行某个脚本固然不错,但如果需要脚本在每天、每周或每月的同一时间运行呢?这时候与其频繁使用at命令,不如利用Linux系统的另一个特性。
Linux系统使用cron程序调度需要定期执行的作业。cron在后台运行,并会检查一个特殊的表(cron时间表),从中获知已安排执行的作业。
1.cron时间表
cron时间表通过一种特别饿格式指定作业何时运行,其格式如下:
minutepasthour hourofday dayofmonth month dayofweek command
cron时间表允许使用特定值、取值范围(比如1~5)或者通配符(星号)来指定各个字段。如果想在每天的10:15运行一个命令,可以使用如下cron时间表字段:
15 10 * * * command
dayofmonth、month以及dayofweek字段中的通配符表明,cron会在每天10:15执行该命令。要指定一条在每周一的下午4:15(4:15 p.m.)执行的命令,可以使用军事时间(1:00 p.m.是13:00,2:00 p.m.是14:00,3:00 p.m.是15:00,以此类推),如下所示:
15 16 * * 1 command
可以使用三字符的文本值(mon、tue、web、thu、fri、sat、sun)或数值(0或7代表周日,6代表周六)来指定dayofweek字段。
这里还有另一个例子。要想在每月第一天的中午12点执行命令,可以使用下列字段:
00 12 1 * * command
dayofmonth字段指定的是月份中的日期值(1~31)。
提示 聪明的你可能会思考,如何设置才能让命令在每月的最后一天执行,因为无法设置一个dayofmonth值,涵盖所有月份的最后一天。常用的解决方法是加一个if-then语句,在其中使用date命令检查明天的日期是不是某个月份的第一天(01):
00 12 28-31 * * if [ "$(date +%d -d tomorrow)" = 01 ] ; then command ; fi
这行脚本会在每天中午12点检查当天是不是当月的最后一天(28~31),如果是,就由cron执行command。
另一种方法是将command替换成一个控制脚本(controlling script),在可能是每月最后一天的时候运行。控制脚本包含if-then语句,用于检查第二天是否为某个月的第一天。如果是,则由控制脚本发出命令,执行必须在当月最后一天执行的内容。
命令列表必须指定要运行的命令或脚本的完整路径。你可以像在命令行中那样,添加所需的任何选项和重定向符:
15 10 * * * /home/christine/backup.sh > backup.out
cron程序会以提交作业的用户身份运行该脚本,因此你必须有访问该脚本(或命令) 以及输出文件的合理权限。
2.构建cron时间表
每个用户(包括root用户)都可以使用自己的cron时间表运行已安排好的任务。Linux提供了crontab命令来处理cron时间表。要列出已有的cron时间表,可以用-l选项:
bash">$ crontab -l
no crontab for christine
$
在默认情况下,用户的cron时间表文件并不存在。可以使用-e选项向cron时间表添加字段。在添加字段时,crontab命令会启动一个文本编辑器,使用已有的cron时间表作为文件内容(如果时间表不存在,就是一个空文件)。
3.浏览cron目录
如果创建的脚本对于执行时间的精确性要求不高,则用预配置的cron脚本目录会更方便。预配置的基础目录共有4个:hourly、daily、monthly和weekly。
bash">$ ls /etc/cron.*ly
/etc/cron.daily:
0anacron apt-compat cracklib-runtime logrotate [...]
apport bsdmainutils dpkg man-db [...]/etc/cron.hourly:/etc/cron.monthly:
0anacron/etc/cron.weekly:
0anacron man-db update-notifier-common
$
如果你的脚本需要每天运行一次,那么将脚本复制到daily目录,cron就会每天运行它。
4.anacron程序
cron程序唯一的问题是它假定Linux系统是7 x 24小时运行的。除非你的Linux运行在服务器环境,否则这种假设未必成立。
如果某个作业在cron时间表中设置的运行时间已到,但这时候Linux系统处于关闭状态,那么该作业就不会运行。当再次启动系统时,cron程序不会再去运行那些错过的作业。为了解决这个问题,许多Linux发行版提供了anacron程序。
如果anacron判断出某个作业错过了设置的运行时间,它会尽快运行该作业。这意味着如果Linux系统关闭了几天,等到再次启动时,原计划在关机期间运行的作业会自动运行。有了anacron,就能确保作业一定能运行,这正是通常使用anacron代替cron调度作业的原因。
anacron程序只处理位于cron目录的程序,比如/etc/cron.monthly。它通过时间戳来判断作业是否在正确的计划间隔内运行了。每个cron目录都有一个时间戳文件,该文件位于/var/spool/anacron:
bash">$ ls /var/spool/anacron
cron.daily cron.monthly cron.daily
$
$ sudo cat /var/spool/anacron/cron.daily
[sudo] password for christine:
20250208
$
anacron程序使用自己的时间表(通常位于/etc/anacrontab)来检查作业目录:
bash">$ cat /etc/anacrontab
# /etc/anacrontab: configuration file for anacron# See anacron(8) and anacrontab(5) for details.SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
HOME=/root
LOGNAME=root# These replace cron's entries
1 5 cron.daily run-parts --report /etc/cron.daily
7 10 cron.weekly run-parts --report /etc/cron.weekly
@monthly 15 cron.monthly run-parts --report /etc/cron.monthly
$
anacron时间表的基本格式和cron时间表略有不同:
period delay identifier command
period字段定义了作业的运行频率(以天为单位)。anacron程序用该字段检查作业的时间戳文件。delay字段指定了在系统启动后,anacron程序需要等待多少分钟再开始运行错过的脚本。
注意 anacron不会运行位于/etc/cron.hourly目录的脚本。这是因为anacron并不处理执行时间需求少于一天的脚本。
identifier字段是一个独特的非空字符串,比如cron.weekly。它唯一的作用是标识出现在日志消息和错误email中的作业。command字段包含了run-parts程序和一个cron脚本目录名。run-parts程序负责运行指定目录中的所有脚本。
at、cron和anacron在调度作业运行方面各占有一席之地。然而,你可能希望在用户启动新的bash shell而不是特定时刻执行某个脚本。