Linux 任务计划

Linux 任务计划

我们每天都可能做重复性的工作,对于Linux系统也是一样,重复性的检查磁盘,同步数据库等工作。这些重复性的工作每个系统都有相应的服务来自动完成,Linux系统上使用crond和atd这两个服务来完成,这种使用服务来完成列行性的工作称之为任务计划。

Linux系统上根据执行的周期将任务计划分为两种

  • 一次性任务计划:执行一次结束。
    使用工具at完成
  • 周期性任务计划:根据定义的周期,周期性执行。
    使用工具crontab完成

一次性任务计划 at

若我们的任务将来只需执行一次,就可以使用at这个工具来完成,at是由atd服务提供的,在CentOS6上atd服务的路径是/etc/rc.d/init.d/atd,CentOS7上atd服务的路径是/usr/lib/systemd/system/atd.service。atd服务是系统守护进程,默认情况下开机则自动启动。若未启动,我们可以使用命令CentOS6上service atd start,CentOS7上systemctl start atd来将atd服务启动。

任务计划到所设定时间到后将执行,执行的时候很可能我们并不在服务器旁边,所以系统默认将执行结果(标准输出)通过邮件(Linux系统自带的邮件服务)发给我们。我们可以通过邮件查看执行结果。

at 配置文件路径
我们通过at工具所创建的任务计划并不会因为关机等操作而丢失,这是因为系统将任务计划通过文件的方式保存到了磁盘上。at所创建的任务计划保存路径是:/var/spool/at/,每个任务计划使用一个文件来保存。

[root@localhost app]# ll /var/spool/at/
total 8
-rwx------. 1 root root 2831 Aug 24 09:04 a00001017e5eb8
-rwx------. 1 root root 2832 Aug 24 09:05 a00002017e6462

at 创建一次性任务计划

  • at TIME 标准输入

at是通过标准输入来接收任务的,所以任何的标准输入方式都可以用到at上,我们可以通过管道,或重定向来获取标准输入(如<,<< 重定向)。

TIME: 时间,定义任务计划执行的时间,时间格式常用的有以下两种

  • HH:MM [YYYY-mm-dd]
  • now+#{minutes, hours, days, weeks}

通过键盘输入任务内容,使用Ctrl+DCtrl+\结束退出。

例子:8月25日00:30将/etc/目录下的文件备份到/app目录下,要求备份文件名字格式为backup-YYYY-MM-DD,且日期为备份时的前一天

方法1: 通过键盘将任务计划输入

[root@localhost app]# at 00:30 2017-08-25
at> tar -jcf /app/backup-`date -d "-1 day" "+%F"`.tar.bz2 /etc/ &> /dev/null
at> <EOT>
job 8 at Fri Aug 25 00:30:00 2017

方法2: 通过文件重定向输入

[root@localhost app]# cat file.at 
tar -jcf /app/backup-`date -d "-1 day" "+%F"`.tar.bz2 /etc/ &> /dev/null
[root@localhost app]# at 00:30 2017-08-25 < file.at

方法三:通过管道输入

[root@localhost app]# tar -jcf /app/backup-`date -d "-1 day" "+%F"`.tar.bz2 /etc/ &> /dev/null | at now+30minutes
job 10 at Thu Aug 24 10:20:00 2017

at 查看任务计划内容
可以通过三种方式查看任务计划内容

  • at -l
    查看有多少个未执行的任务计划,root用户可以查看所有用户的任务计划,而普通用户只能查看自己的。
  • at -c JOB_NUM
    查看具体任务内容,JOB_NUM为任务计划编号
  • cat /etc/spool/at/JOB_NAME
    直接查看任务计划保存的文件
# at -l
[root@localhost app]# at -l
9 Fri Aug 25 00:30:00 2017 a root
10 Thu Aug 24 10:20:00 2017 a root
11 Thu Aug 24 10:10:00 2017 a root
12 Thu Aug 24 12:12:00 2017 a zhubiao

# at -c JOB_NUM
[root@localhost app]# at -c 9 | tail
...
cd /app || {
echo 'Execution directory inaccessible' >&2
exit 1
}
${SHELL:-/bin/sh} << 'marcinDELIMITER744609c9'
tar -jcf /app/backup-`date -d "-1 day" "+%F"`.tar.bz2 /etc/ &> /dev/null

# cat /var/spool/at/JOB_NAME
[root@localhost app]# cat /var/spool/at/a00009017e621e | tail -n 3
tar -jcf /app/backup-`date -d "-1 day" "+%F"`.tar.bz2 /etc/ &> /dev/null

marcinDELIMITER744609c9

at 强制发送邮件
默认情况下,仅有标准输出时,at执行任务计划才发送邮件给创建该任务计划的用户,若所执行的任务没有标准输出,则不发送邮件。若我们要求系统必须发送邮件,则使用-m选项

[root@localhost app]# at 11:12
at> ^C[root@localhost app]# at -m 11:12
at> useradd user1
at> ^\Quit

at 控制用户创建任务计划
有时,我们不希望所有普通用户都能够创建任务计划。我们可以通过两种方式来实现

  • 方法一:将用户添加到”白名单”中,仅有在名单中的用户可以创建任务计划,其它所有用户均不可以,at的白名单文件为/etc/at.allow
  • 方法二:将不允许创建任务计划的用户添加到”黑名单”中,除了黑名单外的所有用户都可以创建任务计划,at的黑名单文件为/etc/at.deny

注意:

  1. 默认情况下CentOS系统中只有文件/etc/at.deny,若我们需要白名单文件/etc/at.allow,需要自己创建。
  2. 若文件/etc/at.deny和/etc/at.allow同时存在,则系统只会读取文件/etc/at.allow的内容,/etc/at.deny文件将失效。
  3. 若文件/etc/at.deny,/etc/at.allow均不存在,则只有root用户可以创建任务计划

at 删除任务计划
删除任务计划可以通过以下两种方式完成

  • at -d JOB_NUM
  • atrm JOB_NUM
    JOB_NUM为任务编号,可以通过at -l查看
# at -d JOB_NUM
[root@localhost app]# at -l
12 Thu Aug 24 12:12:00 2017 a zhubiao
14 Thu Aug 24 11:12:00 2017 a root
[root@localhost app]# at -d 12

# atrm JOB_NUM
[root@localhost app]# at -l
14 Thu Aug 24 11:12:00 2017 a root
[root@localhost app]# atrm 14
[root@localhost app]# at -l

cron 周期性任务计划

at 只能创建一次性任务计划,有些工作是需要重复性执行的,我们可以通过cron来创建周期性任务计划。

cron任务计划分为两种,一种是系统任务计划,另一种是用户任务计划,这两种创建任务计划的方式不同。系统任务计划是通过编辑文件/etc/crontab来实现,用户任务计划使用命令crontab来创建

cron是通过crond服务来实现的,crond服务一样是系统守护进程,当系统启动时启动,该服务每隔一分钟会去检查一下cron中的任务计划文件,看是否有需要执行的任务计划,若有则执行。

crond 配置文件
任务计划文件存放路径
系统:/etc/crontab
配置文件:/etc/cron.d/*
用户:/var/spool/cron/*
cron日志文件:/var/log/cron
控制用户创建任务计划:/etc/cron.deny, /etc/cron.allow

如配置文件所示,cron会每分钟检查一次下面这三个文件或路径下面的任务计划,到时则执行,但错过了时间需通过调用anacron来执行指定目录下的任务计划,详细内容将在后文介绍

  • /etc/crontab
  • /var/spool/cron/*
  • /etc/cron.d/*

cron 系统任务计划

系统任务计划的创建,查看,删除均通过文件/etc/crontab来完成,我们首先看一下该文件的编辑格式

[root@localhost app]# cat /etc/crontab 
SHELL=/bin/bash <=默认shell类型为bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin <=在该文件中命令的执行路径,此处注意所执行的命令是否在该路径下,若没有在需将路径添加到此处
MAILTO=root <=将执行的标准输出内容发送邮件给哪一位用户

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed

如上所示,编辑cron系统任务计划时有三部分内容:执行周期,用户,命令

  • 执行周期
    执行周期有两种表示方法

方法一:使用5个字段来表示
minute hour day_of_mount month day_fo_week

每个字段有以下几种表示方法
* 表示”每”,比如在minute字段,则表示每分钟
*/NUM 表示“每NJUM” 比如在minute字段*/2表示每2分钟
NUM1, NUM2, ... 离散的时间点,在NUM1, NUM2 … 这几个时间点执行,比如在minute字段1,3,7表示在第1,3, 7分钟时执行。
NUM1-NUM2 一段离散的时间点,在NUM1-NUM2这几个整数时间点执行,比如在minute字段33-35表示在第33,34, 35分钟时执行。

方法二 : 使用下面几个字符串来表示

字符串 等同于 含义
@reboot 系统重新启动,当启动到crond服务时执行
@yearly 0 0 1 1 * 每年的1月1日 00:00 执行
@annually 0 0 1 1 * 每年的1月1日 00:00 执行
@monthly 0 0 1 * * 每月的1日 00:00 执行
@weekly 0 0 * * 0 每周的周日 00:00执行
@daily 0 0 * * * 每天 00:00执行
@hourly 0 * * * * 每小时 执行一次

例子:工作日内,每10分钟检查一次硬盘,若硬盘使用空间高于80%则使用wall报警

# 编写实现该功能的脚本文件
[root@localhost app]# cat memory.sh
warning=`df | awk -F" " '/\/dev\/sda/{a=substr($5,1,length($5)-1)+0; if(a>80){print $1,a}}'`

if [ -n "$warning" ];then
wall "$warning"
fi

# 将脚本文件添加到任务计划中
[root@localhost app]# cat /etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

...

*/10 * * * 1,2,3,4,5 root /bin/bash /app/memory.sh
  • 命令
    执行命令有两种情况

第一种
若执行的命令较少,我们可以将需要执行命令直接写在/etc/crontab中命令字段处,各命令之间使用符号 ; 做分隔。
* * * * * user-name COMMAND

第二种
若所执行的命令较多,或是一批脚本文件,此时我们再使用第一种方法是不适宜的,我们可以将所需执行的脚本放到一个固定的目录中,然后将目录路径添加到/etc/crontab中命令字段位置,具体格式如下
* * * * * user-name run-parts DIRECTORY

例如系统上/etc/cron.d/0hourly 所定义的格式就是如此

[root@CentOS6 cron.d]# cat /etc/cron.d/0hourly 
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
01 * * * * root run-parts /etc
/cron.hourly

cron 用户任务计划

对于普通用户,是没有权限编辑 /etc/crontab 文件的,甚至连普通用户所创建的任务计划文件所保存的目录都没任何权限。所以普通用户不可能直接编辑 /etc/crontab 来创建任务计划。
普通用户创建、删除、修改、查看任务计划是通过命令crontab来实现的,所创建的任务计划以文件的形式保存在目录 /var/spool/cron/ 下,文件名为 /var/spool/cron/USER,也许你会很奇怪,普通用户对目录/var/spool/cron/ 不是没有任何权限吗,如何在此目录下创建、删除文件呢? 问题出在命令crontab身上,crontab具有SUID权限,普通用户对该命令具有执行权限,执行该命令执行时是以该命令的属主root的身份对文件进行读、写操作的。

# 普通用户对/etc/crontab 无写权限
[root@CentOS6 cron.d]# ll /etc/crontab
-rw-r--r--. 1 root root 457 Aug 22 22:02 /etc/crontab

# 普通用户对目录 /var/spool/cron/ 没任何权限
[root@CentOS6 cron.d]# ll -d /var/spool/cron/
drwx------. 2 root root 4096 Aug 22 22:13 /var/spool/cron/

# crontab 具有SUID权限
[root@CentOS6 cron.d]# ll `which crontab`
-rwsr-xr-x. 1 root root 51784 Aug 24 2016 /usr/bin/crontab

用户任务计划创建、修改、删除、查看命令均使用crontab,语法格式如下:
crontab [-u USER] [-e | -r | -l]

  • 创建用户任务计划
    crontab -e [-u USER]

用户任务计划的格式相较于系统任务计划的格式,少了user-name字段,其它均相同

用户任务计划格式

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * command to be executed

实例: 每周2, 4, 7备份/var/log/messages文件至/app目录中,文件名形如“messages-yyyymmdd”

[zhubiao@CentOS6 cron.d]$ crontab -e
0 0 * * 2,4,0 cp -a /var/log/messages /app/messages-`date "+\%Y\%m%\d"`
  • 查看用户任务计划
    crontab -l [-u USER]
# 查看当前用户任务计划
[root@localhost ~]# crontab -l
*/10 * * * * /bin/bash /app/1.sh

# 查看指定用户的任务计划
[root@localhost ~]# crontab -u zhubiao -l
*/5 * * * * ls &> /dev/null
  • 删除用户任务计划
    crontab -r [-i ] [-u USER]
# 删除用户zhubiao的任务计划
[root@localhost ~]# crontab -r -u zhubiao

# 查看已被删除
[root@localhost ~]# crontab -l -u zhubiao
no crontab for zhubiao

anacron

若因各种原因导致自己所定的任务计划错过了执行时间,比如执行任务计划时间点的时候你关机了。那么我们错过的任务计划还会不会执行呢? 这就分两种情况啦,CentOS系统上若你的任务计划是放到 /etc/cron.daily,/etc/cron.weekly,/etc/cron.monthly这三个目录下面的话,那就有可能在一天,一周,一个月内被重新执行啦,若不在这三个目录内,则错过就错过啦。这是如何实现的呢,请看下文解说:

Alt text

  1. crond服务会每分钟检查一次/etc/crontab,/var/spool/cron/,/etc/cron.d/ 这三处文件或目录下的任务计划,当然就能检查到/etc/cron.d/0hourly文件。

/etc/cron.d/0hourly 中的内容如下:

[root@localhost cron.d]# cat /etc/cron.d/0hourly
# Run the hourly jobs
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
01 * * * * root run-parts /etc/cron.hourly

按照 /etc/cron.d/0hourly 所定义的系统任务计划,crond将每小时执行一次/etc/cron.hourly/目录下的任务

  1. 在/etc/cron.hourly/ 目录下有一个文件0anacron,该文件中定义了满足什么条件就执行anacron -s命令

/etc/cron.hourly/0anacron,内容如下:

[root@localhost cron.d]# cat /etc/cron.hourly/0anacron
#!/bin/sh
# Check whether 0anacron was run today already
if test -r /var/spool/anacron/cron.daily; then
day=`cat /var/spool/anacron/cron.daily` <=取上次记录的执行日期
fi
if [ `date +%Y%m%d` = "$day" ]; then <=若上次记录的日期与今天是同一天,则退出
exit 0;
fi

# Do not run jobs when on battery power
if test -x /usr/bin/on_ac_power; then
/usr/bin/on_ac_power >/dev/null 2>&1 <=检查是否为电池供电,电池供电则不执行。
if test $? -eq 1; then
exit 0
fi
fi
/usr/sbin/anacron -s <=执行anacron命令
  1. anacron根据配置文件执行相应目录下的任务计划
    anacron配文件如下:
    /etc/anacrontab
    /var/spool/anacron/

anacron根据/etc/anacrontab中所定义周期,目录去检查执行相应错过执行的时间的任务计划
anacrontab中的内容如下

[root@CentOS6 ~]# cat /etc/anacrontab 
# /etc/anacrontab: configuration file for anacron

# See anacron(8) and anacrontab(5) for details.

SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45 <=随机延迟的分钟数
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22 <=延迟时间在3-22个小时内执行

#period in days delay in minutes job-identifier command
1 5 cron.daily nice run-parts /etc/cron.daily
7 25 cron.weekly nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly nice run-parts /etc/cron.monthly

period in days : 执行周期,一天为单位
delay:延迟时间,以分钟为单位
job-identifier :工作文件名,与 /var/spool/anacron/下的文件名对应
command : 执行的命令

anacrontab中定义了需检查的任务计划目录,但anacron是如何知道这些目录下的任务计划有没有错过执行时间呢,这就是通过分析这些目录下的任务计划执行周期,然后与/var/spool/anacron/目录文件中所记录的上次执行时间进行对比做出的判定。

# 分别记录了每天,每周,每月中的上次执行时间,与anacrontab中的定义相对应。
[root@localhost cron.d]# ls -1 /var/spool/anacron/*
/var/spool/anacron/cron.daily
/var/spool/anacron/cron.monthly
/var/spool/anacron/cron.weekly

[root@localhost cron.d]# cat /var/spool/anacron/*
20170826
20170822
20170822

经过anacron的检查,若发现错过了执行周期,则按照anacrontab中所定义的时间段内执行错过的任务计划。