at 在未来只做一次任务调度,依赖于 atd 服务;batch 命令被用来在系统平均负载达到 0.8% 以下时执行一次性的任务;cron 在未来周期性的执行任务,依赖于 crond 服务;anacron 用于在开机时执行上次停机时没有完成的 crontab 任务,依赖于anacron服务。

at

at 命令被用来在指定时间内调度一次性的任务,依赖于 atd 服务。其安装方式为

# debian
apt-get install at

# centos
yum -y install at

at 命令的选项与参数:

-m :当 at 的任务完成后,即使没有输出信息,也以 email 通知给使用者
-l :列出目前系统上面的所有该使用者的 at 任务(同 atq)
-r :可以取消一个 at 任务(同 atrm)
-c :可以列出后面接的该项任务的内容
-v :可以使用较明显的时间格式列出 at 任务
-V : 显示版本信息
-q <列队> : 使用指定的列队
-f <文件> : 从指定文件读入任务而不是从标准输入读入
-t <时间参数> : 以时间参数的形式提交要运行的任务

at 命令的时间格式:

now + 时间: 时间以 minutes、hours、days、或 weeks 为单位
HH:MM: 24 小时制度,如果时间已过,就会在第二天的这一时间执行
midnight: 表示 00:00
noon: 表示 12:00
teatime: 表示 16:00

另外,还有 atq 命令用于查看当前等待运行的任务(相当于 at -l),atrm 命令用于删除指定的任务(相当于 at -r)。

示例:

# 一分钟后执行
echo "echo 'hello'" | at now +1 minutes

# 五天后执行
echo "echo 'hello'" | at now +5 days

# 上午 at 11:20 AM 执行
echo "echo 'hello'" | at 11:20 AM

# 在指定时间关机
echo "/sbin/shutdown -h now" | at 02:00 2013-07-26

# 明天 17:20 执行
$ at 17:20 tomorrow
at> date >/root/2013.log
at> <EOT>                      # 按 Ctrl+D 结束输入
job 8 at 2013-01-06 17:20

# 查看任务列表
$ at -l
3       Tue Jan 25 10:47:00 2022 a root
$ atq
3       Tue Jan 25 10:47:00 2022 a root

# 查看任务内容
$ at -c 3

# 删除任务
$ at -r 3

batch

batch 命令被用来在系统平均负载达到 0.8% 以下时执行一次性的任务,通俗的所就是系统空闲时执行任务。用法与 at 一样 。

需要注意的是, /etc/at.allow/etc/at.deny 文件可用来限制对 at 和 batch 命令的使用(root 用户不受其控制)。这两个使用控制文件的格式都是每行一个用户(不允许空格),且文件修改后,atd 守护进程不需重启。如果 at.allow 文件存在,那么只有其中列出的用户才被允许使用 at 或 batch 命令,且忽略 cron.deny 文件。如果 at.allow 文件不存在,那么所有在 cron.deny 中列出的用户都将禁止使用 at 和 batch。

crontab

Crontab 用于定时的执行某项任务,其运行通过 cron 守护进程来完成,进程每分钟会定期检查是否有要执行的任务。

cron 服务通过 crontab 命令来设定具体的周期性任务,我们可以在固定的间隔时间执行指定的系统指令或一些脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令对于需要周期性运行的程序是非常有用的。

命令格式:

crontab [-u user] file
crontab [ -u user ] [ -i ] { -e | -l | -r }

参数说明:

  • -u user:用来设定某个用户的crontab服务;
  • file:file 是命令文件的名字,表示将 file 做为 crontab 的任务列表文件并载入 crontab。如果在命令行中没有指定这个文件,crontab 命令将接受标准输入(键盘)上键入的命令,并将它们载入 crontab。
  • -e:编辑某个用户的 crontab 文件内容。如果不指定用户,则表示编辑当前用户的 crontab 文件。
  • -l:显示某个用户的 crontab 文件内容,如果不指定用户,则表示显示当前用户的 crontab 文件内容。
  • -r:从 /var/spool/cron 目录中删除某个用户的 crontab 文件,如果不指定用户,则默认删除当前用户的 crontab 文件。
  • -i:在删除用户的 crontab 文件时给确认提示。

需要注意的是,Linux 下的 cron 任务调度分为两类:系统任务调度和用户任务调度。

系统任务

系统周期性所要执行的工作,比如写缓存数据到硬盘、日志清理等。在 /etc 目录下有一个 crontab 文件,这个就是系统任务调度的配置文件。 /etc/crontab 文件包括下面几行:

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user   command
17 *    * * *    root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *    root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7    root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *    root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#

crontab 的文件格式:

  • 第1列 分钟0~59
  • 第2列 小时0~23(0表示子夜)
  • 第3列 日1~31
  • 第4列 月1~12
  • 第5列 星期0~6(0表示星期天)
  • 第6列 运行命令的用户
  • 第7列 要运行的命令

时间配置的特殊字符:

  • 星号( * ):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。
  • 逗号( , ):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”
  • 中杠( - ):可以用整数之间的中杠表示一个整数范围,例如 “2-6” 表示 “2,3,4,5,6”
  • 正斜线( / ):可以用正斜线指定时间的间隔频率,例如 “0-23/2” 表示每两小时执行一次。同时正斜线可以和星号一起使用,例如 */10,如果用在 minute 字段,表示每十分钟执行一次。

实际上,一般不建议直接修改 /etc/crontab 文件,而是在 /etc/cron.d 目录下创建额外的配置,该目录下的配置文件格式与 /etc/crontab 相同。但需要注意的是,此目录下的文件有命名规则,文件名由大小写字母,数字,横杠和下划线组成,不能包括圆点, 且文件所有者必须是 root:root,文件权限必须是 644(-rw-r--r--),为软链接时,链接的文件所有者也必须是 root

用户任务

用户定期要执行的工作,比如用户数据备份、定时邮件提醒等。用户可以使用 crontab 工具来定制自己的计划任务。所有用户定义的 crontab 件都被保存在 /var/spool/cron 目录中,用户任务的配置文件中不需要指定运行用户字段。其文件名与用户名一致,使用者权限文件如下:

  • /etc/cron.deny 该文件中所列用户不允许使用 crontab 命令
  • /etc/cron.allow 该文件中所列用户允许使用 crontab 命令
  • /var/spool/cron/ 所有用户 crontab 文件存放的目录,以用户名命名

可以用 crontab -e 来编辑用户任务,EDITOR 环境变量可以用来指定编辑器,例如设置编辑器为 vim:

export EDITOR=vim;

NOTE: 在 vim 下如果要查看当前文件所在路径,可以在命令模式下先按数字 1,然后按下组合键 CTRL + G,这样可以看到当前编辑的 crontab 文件其实是一个临时文件。

一些配置示例:

# 每 1 分钟执行一次 command
* * * * * command

# 每小时的第 3 和第 15 分钟执行
3,15 * * * * command

# 在上午 8 点到 11 点的第 3 和第 15 分钟执行
3,15 8-11 * * * command

# 每隔两天的上午 8 点到 11 点的第 3 和第 15 分钟执行
3,15 8-11 */2  *  * command

# 每周一上午 8 点到 11 点的第 3 和第 15 分钟执行
3,15 8-11 * * 1 command

# 每月最后一天执行
55 23 28-31 * * [[ "$(date --date=tomorrow +\%d)" == "01" ]] && command

# 每月 1、10、22 日的 04:45 重启 nginx
45 4 1,10,22 * * sudo service nginx restart

# 每周六、周日的 01:10 重启 nginx
10 1 * * 6,0 sudo service nginx restart

经验总结

系统级任务配置

系统级任务调度主要完成系统的一些维护操作,用户级任务调度主要完成用户自定义的一些任务,可以将用户级任务调度放到系统级任务调度来完成(不建议这么做),但是反过来却不行。root 用户的任务调度操作可以通过 crontab -u root –e 来设置,也可以将调度任务直接写入 /etc/crontab 文件。需要注意的是,如果要定义一个定时重启系统的任务,就必须将任务放到 /etc/crontab 文件,即使在 root 用户下创建一个定时重启系统的任务也是无效的。

重定向输出

每条任务调度执行完毕,系统都会将任务输出信息通过电子邮件的形式发送给当前系统用户,这样日积月累,日志信息会非常大,可能会影响系统的正常运行,因此,将每条任务进行重定向处理非常重要。 例如,可以在 crontab 文件中设置如下形式,忽略日志输出:

0 */3 * * * /usr/local/apache2/apachectl restart >/dev/null 2>&1

/dev/null 2>&1 表示先将标准输出重定向到 /dev/null,然后将标准错误重定向到标准输出,&1 表示文件描述符 1。由于标准输出已经重定向到了 /dev/null,因此标准错误也会重定向到 /dev/null,这样日志输出问题就解决了。

或者将输出定向到文件,以便于在出错时定为问题:

* * * * * command >>/tmp/command.log 2>>command.err

无法同时指定月和星期

另一个需要注意的地方在于,你可以分别以周或者是日月为单位作为循环,但你不可使用 几月几号且为星期几 的模式工作,也就是说周与日月不可同时并存。

设置秒级任务

如果要设置秒级别的任务,可以通过 sleep 命令来完成,例如每 30 秒请求下 /tmp 目录

* * * * * rm -rf /tmp/*
* * * * * sleep 30; rm -rf /tmp/*

另外,还可以利用 $RANDOM 环境变量来 sleep 一个随机时间,例如:

# 随机休眠 0 - 60 秒
*/3 * * * * sleep $((RANDOM \% 60)); python -c "import datetime; print(datetime.datetime.now())" >> /tmp/test.log

# 随机休眠 5 - 20 秒
*/3 * * * * sleep $(((RANDOM \% 15) + 5)); python -c "import datetime; print(datetime.datetime.now())" >> /tmp/test.log

但这里要注意的是,cron 默认使用 /bin/sh 作为 shell 程序,/bin/sh 指向 dash,其不支持 $RANDOM 变量。所以要使用 $RANDOM 需要设置环境变量 SHELL=/bin/bash,以让 cron 使用 bash 作为 shell 程序。

配置连续的时间

配置连续的时间时,不能跨越起点,如配置小时为 23-2,这样是无效的,因为跨越了起点 0。同样,分钟、日、月、星期的配置都不能跨域起点。缺失需要跨越起点时,应该拆为两个时间段:

* 23,0-2 * * *  command
50-59,0-10 * * * *  command

单独记录 cron 日志

在 Ubuntu 系统上,cron 的日志默认输出到 syslog 中,如果需要将 cron 日志输出到独立的文件中,则应修改 /etc/rsyslog.d/50-default.conf 文件,注释掉如下行的内容:

#cron.*    /var/log/cron.log

然后重启 rsyslog 和 cron 服务:

sudo service rsyslog restart
sudo service cron restart

在 cron 配置中定义变量

在 crontab 的配置中支持引用环境变量,也支持定义变量,但切记 不能在变量定义时引用其他变量,如 B=$A 这样定义时是无效的,变量 A 不会展开,所以 B 的值直接是 $A。这一点与 /etc/environment 文件的配置类似。

使用 % 需要转义

在 crontab 中 % 是有特殊含义的,其表示需要换行。所以在使用 % 时需要用反斜杠转义,如在格式化日志时用 date +%Y%m%d 在 crontab 中不会得到执行,应使用 date +\%Y\%m\%d 代替。

在 /etc/cron.d 中的配置文件有格式要求

如果需要把 cron 的定时任务配置放到 /etc/cron.d 目录中,则需注意该目录下的文件有一定格式要求,其文件名由大小写字母,数字,横杠和下划线组成,特别要注意不能包括圆点, 且文件所有者必须是 root:root,文件权限必须是 644(-rw-r--r--),为软链接时,链接的文件所有者也必须是 root 账户。最好是编写一个脚本来部署该目录下的配置文件,如:

# deploy cron config file to /etc/cron.d

set -e

for path in $@; do
    dst_path=/etc/cron.d/$(basename $path)
    if [ -f $dst_path ]; then
        mv -v $dst_path ${dst_path}.$(date +"%Y%m%d%H%M%S")
    fi
    cp -v $path /etc/cron.d
    chown -v root:root $dst_path
    chmod -v 644 $dst_path
    md5sum $path
    md5sum $dst_path
    ls -l $dst_path
done

exit 0

anacron

cron 是用来控制循环执行的例行性工作的,可循环的时间为分钟、小时、每周、每月等。比如要设定机器每天早上 8 点进行备份,就可以用到这个服务。除非机器保持每天都 24 小时运行,否则有些系统例行工作就不会被执行,这个时候就可以用到 anacron 了。

anacron 并不是用来取代 cron 的,anacron 存在的目的就在于上面提到的问题,用于处理非 24 小时一直启动的 Linux 系统的 cron 服务的执行!所以 anacron 并不能指定何时执行某项任务, 而是以天为单位或者是在开机后立刻进行 anacron 的动作,他会去侦测停机期间应该进行但是并没有进行的 cron 服务,如果有就将该任务执行一遍,然后就自动停止。

anacron 会以一天、七天、一个月周期去侦测系统中未进行的 crontab 任务,因此对于某些特殊的使用环境非常有帮助。anacron 会去分析现在的时间与时间记录档案所记载的上次运行 anacron 的时间,两者比较后若发现有差异,也就是在某些时刻没有进行 crontab,那么此时 anacron 就会开始执行未运行的 crontab 了。所以 anacron 也是通过 crontab 来运行的,因此 anacron 运行的时间通常有两个,一个是系统启动期间运行,一个是写入 crontab 的排程中,这样才能够在特定时间分析系统未进行的 crontab 工作。我们可以使用 ls -al /etc/cron*/*ana* 的方式来查看 anacron 的侦测时间。但是我们仔细分析该文件的话,会发现它主要是执行 anacron 命令。

anacron 命令的语法如下:

  • (1) -s 开始连续的运行各项工作,会一句时间记录当的数据判断是否进行。
  • (2) -f 强制进行,而不去判断时间登录档的时间戳。
  • (3) -n 立即进行未进行的任务,而不延迟等待时间。
  • (4) -u 仅升级时间记录当的时间戳,不进行任何工作。

而 anacron 的配置文件是 /etc/anacrontab,而它的很多内容则是在 /var/spool/anacron 里面保存。当 anacron 下达 anacron -s cron.daily 时,它会有如下的步骤:

  • (1) 由 /etc/anacrontab 分析到 cron.daily 这项工作名称的天数为一天。
  • (2) 由 /var/spool/anacron/cron.daily 取出最近一次运行 anacron 的时间戳。
  • (3) 把取出的时间戳与当前的时间戳相比较,如果差异超过了一天,那么就准备进行命令。
  • (4) 若准备进行命令,根据 /etc/anacrontab 的配置,将延迟 65 分钟。
  • (5) 延迟时间后,开始运行后续命令,也就是 run-parts /etc/cron.daily 这串命令。
  • (6) 运行完毕后,anacron 程序结束。