第 12 章 Linux 定时任务
12.1、配置 crond 定时任务
关于定时任务,首先需弄清的概念:
- crond 是一个 daemon 类程序,路径为 /usr/sbin/crond。默认会以后台方式启动,service 或 systemd 方式启动 crond 默认也是后台方式的。
- crondtab 是管理 crontab file 的工具,而 crontab file 是定义定时任务条目的文件。
- crontab file 存在于多处,包括系统定时任务文件 /etc/crontab 和 /etc/cron.d/*,还有独属于各用户的任务文件 /var/spool/cron/USERNAME。
再就是 crontab 命令:
-l:列出定时任务条目
-r:删除当前任务列表终端所有任务条目
-i:删除条目时提示是否真的要删除
-e:编辑定时任务文件,实际上编辑的是/var/spool/cron/*文件
-u:操作指定用户的定时任务
执行 crontab -e 命令编辑当前用户的 crontab file,例如当前为 root 用户,则编辑的是 /var/spool/cron/root 文件。例如写入下面这一行。
* * * * * /bin/echo "the first cron entry" >> /tmp/crond.txt
[root@arm64v8 ~]# crontab -e
crontab: installing new crontab
[root@arm64v8 ~]#
[root@arm64v8 ~]# crontab -l
* * * * * /bin/echo "the first cron entry" >>/tmp/crond.txt
[root@arm64v8 ~]#
[root@arm64v8 ~]# cat /var/spool/cron/root
* * * * * /bin/echo "the first cron entry" >>/tmp/crond.txt
[root@arm64v8 ~]#
这将会每分钟执行一次 echo 命令,将内容追加到 /tmp/crond.txt 文件中。
任务计划中的任务条目如何定义,可以查看 /etc/crontab 文件。
[root@arm64v8 ~]# cat /etc/crontab
SHELL=/bin/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
[root@arm64v8 ~]#
在此文件中定义了 3 个变量,其中一个是 PATH,该变量极其重要,任务执行失败 command not found 就跟这有关。在最后还给出了任务条目的定义方式:
- 每个任务条目分为 6 段,每段以空格分隔,之所以此处多了 user-name 段是因为 /etc/crontab 为系统定时任务文件,而一般定时任务是没有该段的。
- 前五段为时间的设定段,分别表示"分时日月周",它们的定义不能超出合理值范围,第六段为所要执行的命令或脚本任务段。
- 在时间定义段中,使用
*
表示每单位,即每分钟,每小时,每天,每月,每周几(仍然是每天)。实际上,按 man 文档中解释,*
表示的是从每个时间段的起始到结尾,也就是全部时间单位的意思。例如在小时上设置*
,表示0, 1, 2, 3 ... 22, 23
的意思。 - 每个时间段中,都可以使用逗号
,
来表示枚举,例如定义0, 30, 50 * * * *
表示每个时辰的整点、第 30 分钟和第 50 分钟都执行该任务。 - 每个时间段中,都可以使用
-
定义范围,可以结合逗号使用。如分钟段定义了00, 20-30, 50
表示每个时辰的整点、第 20 到 30 分钟的每分钟、第 50 分钟都执行该任务。 - 每个时间段中,使用
/
表示忽略时间,如在小时段定义了0-13/2
表示在0/2/4/6/8/10/12
点才满足时间定义。常使用*/N
表示每隔多久的意思。例如00 */2 * * *
表示在每天每隔两小时的整点执行该任务(严格地说是0-23/2
,也就是0,2,4,...,22
,所以凌晨1
点不会执行任务)。 - 如果定义的日和周冲突了,则会多次执行(不包括因为
*
号导致的冲突)。例如每月的 15 号执行该任务,同时又定义了周三执行该任务,正常无冲突情况下,将在周三和每月 15 号执行,但如果某月的 15 号同时是周三,则该任务在此日执行两次。因此,应该尽力避免同时定义周和日的任务。 - 命令段(即第 6 段) 中,不能随意出现百分号
%
,因为它表示换行的特殊意义,且第一个%
后的所有字符串将当作命令的标准输入。
比如:* * * * * /bin/cat >> /tmp/crond.txt %"the first %%cron entry%"
[root@arm64v8 ~]# crontab -e
crontab: installing new crontab
[root@arm64v8 ~]#
[root@arm64v8 ~]# crontab -l
* * * * * /bin/cat >> /tmp/crond.txt %"the second %%cron entry%"
[root@arm64v8 ~]#
该任务输出的结果将是:
[root@arm64v8 ~]# cat /tmp/crond.txt
"the second
cron entry
"
所以,在定时任务条目中若以时间定义文件名时,应当将 % 使用反斜杠转义。如:
* * * * * cp /etc/fstab /tmp/`date +\%Y-\%m-\%d`.txt
注意:使用 *
号问题。例如 * */2 * * *
,它表示每隔两小时后的每一分钟都执行任务,也就是凌晨 0 点的每分钟执行任务,凌晨 1 点不执行任务,凌晨 2 点的每分钟执行任务,凌晨 4 点的每分钟执行任务,依此类推。同理,*/5 */2 * * *
表示每隔 2 小时后的每 5 分钟执行一次任务。
12.1.1、crontab file
crontab file 为任务定义文件。
- 在此文件中,空行会被忽略,首个非空白字符且以 # 开头的行为注释行,但 # 不能出现在行中。
- 可以在 crontab file 中设置环境变量,方式为 "name=value",等号两边的空格可随意,即 "name = value" 也是允许的。但 value 中出现的空格必须使用引号包围。
- 默认 crond 命令启动的时候会初始化所有变量,除了某几个变量会被 crond daemon 自动设置好,其他所有变量都被设置为空值。自动设置的变量包括 SHELL=/bin/sh,以及 HOME 和 LOGNAME(在 CentOS 上则称为 USER),后两者将被默认设置为 /etc/passwd 中指定的值。其中 SHELL 和 HOME 可以被 crontab file 中自定义的变量覆盖,但 LOGNAME 不允许覆盖。当然,自行定义的变量也会被加载到内存。
- 除了 LOGNAME/HOME/SHELL 变量之外,如果设置了发送邮件,则 crond 还会寻找 MAILTO 变量。如果设置了 MAILTO,则邮件将发送给此变量指定的地址,如果 MAILTO 定义的值为空(MAILTO=""),将不发送邮件,其他所有情况邮件都会发送给 crontab file 的所有者。
- 在系统定时任务文件 /etc/crontab 中,默认已定义 PATH 环境变量和 SHELL 环境变量,其中 PATH=/sbin:/bin:/usr/sbin:/usr/bin。
- crond daemon 每分钟检测一次 crontab file 看是否有任务计划条目需要执行。
12.1.2、crond 命令调试
很多时候写了定时任务却发现没有执行,或者执行失败,但因为 crond 是后台运行的,又没有任何提示,很难进行排错。但是可以让 crond 运行在前端并进行调试的。
先说明下任务计划程序 crond 的默认执行方式。
使用下面三条命令启动的 crond 都是在后台运行的,且都不依赖于终端。
[root@arm64v8 ~]# systemctl start crond.service # systemctl 启动 crond
[root@arm64v8 ~]# service crond start # 重定向使用 systemctl 启动 crond
Redirecting to /bin/systemctl start crond.service
[root@arm64v8 ~]#
[root@arm64v8 ~]# crond # 已启动 crond,再启动报错
crond: can't lock /var/run/crond.pid, otherpid may be 780: Resource temporarily unavailable
[root@arm64v8 ~]#
但 crond 是允许接受选项的:
crond [-n] [-P] [-x flags]
选项说明:
-n:让crond以前端方式运行,即不依赖于终端。
-P:不重设环境变量PATH,而是从父进程中继承。
-x:设置调试项,flags是调试方式,比较有用的方式是test和sch,即"-x test"和"-x sch"。
:其中test调试将不会真正的执行,sch调试显示调度信息,可以看到等待时间。具体的见下面的示例。
先看看启动脚本启动 crond 的方式。
[root@arm64v8 ~]# cat /lib/systemd/system/crond.service
[Unit]
Description=Command Scheduler
After=auditd.service systemd-user-sessions.service time-sync.target
[Service]
EnvironmentFile=/etc/sysconfig/crond
ExecStart=/usr/sbin/crond -n $CRONDARGS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
[Install]
WantedBy=multi-user.target
[root@arm64v8 ~]#
它的环境配置文件为 /etc/sysconfig/crond,该文件中什么也没设置。
[root@arm64v8 ~]# cat /etc/sysconfig/crond
# Settings for the CRON daemon.
# CRONDARGS= : any extra command-line startup arguments for crond
CRONDARGS=
[root@arm64v8 ~]#
所有它的启动命令为:/usr/sbin/crond -n。但尽管此处加了 "-n" 选项,crond 也不会前端运行,且不会依赖于终端,这是 systemctl 决定的。
再解释下如何进行调试。以下面的任务条目为例。
[root@arm64v8 ~]# crontab -l
* * * * * echo "hello world" >> /tmp/hello.txt
[root@arm64v8 ~]#
执行 crond 并带上调试选项 test。
[root@arm64v8 ~]# crond -x test
debug flags enabled: test
[19547] cron started
log_it: (CRON 19547) INFO (RANDOM_DELAY will be scaled with factor 28% if used.)
log_it: (CRON 19547) INFO (running with inotify support)
log_it: (CRON 19547) INFO (@reboot jobs will be run at computer's startup.)
log_it: (root 19551) CMD (echo "hello world" >> /tmp/hello.txt)
执行 crond 并带上调试选项 sch。
[root@arm64v8 ~]# crond -x sch
debug flags enabled: sch
[19559] cron started
log_it: (CRON 19559) INFO (RANDOM_DELAY will be scaled with factor 8% if used.)
log_it: (CRON 19559) INFO (running with inotify support)
[19559] GMToff=28800
log_it: (CRON 19559) INFO (@reboot jobs will be run at computer's startup.)
[19559] Target time=1628343840, sec-to-wait=7 # 等待crond daemon下一次的检测,所以表示7秒后crond将检测crontab file
user [root:0:0:...] cmd="echo "hello world" >> /tmp/hello.txt"
Minute-ly job. Recording time 1628315041
[19559] Target time=1628343900, sec-to-wait=60
log_it: (root 19562) CMD (echo "hello world" >> /tmp/hello.txt)
但要注意,在 sch 调试结果中的等待时间是 crond 这个 daemon 的检测时间,所以它表示等待下一次检测的时间,因此除了第一次,之后每次都是 60 秒,因为默认 crond 是每分钟检测一次 crontab file 的。
例如,下面是某次的等待结果,在这几次等待检测过程中没有执行任何任务。
[19567] Target time=1628344380, sec-to-wait=2
[19567] Target time=1628344440, sec-to-wait=60
[19567] Target time=1628344500, sec-to-wait=60
还可以同时带多个调试方式,如:
[root@arm64v8 ~]# crond -x sch,test
debug flags enabled: sch test
[19576] cron started
log_it: (CRON 19576) INFO (RANDOM_DELAY will be scaled with factor 32% if used.)
log_it: (CRON 19576) INFO (running with inotify support)
[19576] GMToff=28800
log_it: (CRON 19576) INFO (@reboot jobs will be run at computer's startup.)
[19576] Target time=1628344500, sec-to-wait=6
user [root:0:0:...] cmd="echo "hello world" >> /tmp/hello.txt"
Minute-ly job. Recording time 1628315701
[19576] Target time=1628344560, sec-to-wait=60
log_it: (root 19579) CMD (echo "hello world" >> /tmp/hello.txt)
user [root:0:0:...] cmd="echo "hello world" >> /tmp/hello.txt"
[19576] Target time=1628344620, sec-to-wait=60
Minute-ly job. Recording time 1628315761
log_it: (root 19587) CMD (echo "hello world" >> /tmp/hello.txt)
user [root:0:0:...] cmd="echo "hello world" >> /tmp/hello.txt"
Minute-ly job. Recording time 1628315821
[19576] Target time=1628344680, sec-to-wait=60
这样在调试定时任务时间时,也不会真正执行命令。
12.2、配置 at 定时任务
at 套件命令语法:
at [-V] [-q queue] [-f file] [-mMlv] timespec...
at [-V] [-q queue] [-f file] [-mMkv] [-t time]
at -c job [job...]
atq [-V] [-q queue]
at [-rd] job [job...]
atrm [-V] job [job...]
batch
at -b
-m: 当指定的任务被完成之后,将给用户发送邮件,即使没有标准输出
-I: atq的别名
-d: atrm的别名
-v: 显示任务将被执行的时间
-c: 打印任务的内容到标准输出
-V: 显示版本信息
-q<列队>: 使用指定的列队
-f<文件>: 从指定文件读入任务而不是从标准输入读入
-t<时间>: 以时间参数的形式提交要运行的任务
- at and batch:从标准输入读取命令或者使用默认 /bin/sh 读取脚本文件,在指定时刻正式执行。
- at:在指定的时间执行命令。
- batch:在系统负载允许的情况下执行命令,一般是系统负载小于 0.8 或者 atd 指定的值。
- atq:如果是 root 用户,列出所有用户的待执行 jobs,输出格式:Job number, date, hour, queue, and username。
- atrm:删除 jobs,通过 job number 标识。
at
允许使用一套相当复杂的指定时间的方法。
- hh:mm(小时:分钟):当天执行,时间过了,第二天执行。
- midnight,noon,or teatime (4pm):模糊指定时间。
- AM or PM:通过 12 小时制指定时间。
- MMDD[CC]YY, MM/DD/[CC]YY, DD.MM.[CC]YY or [CC]YY-MM-DD:常规的年月日表示方式指定时间。
- now + count time-units:now 就是当前时间,time-units 是时间单位,这里能够是 minutes(分钟)、hours(小时)、days(天)、weeks(星期)。count 是时间的数量,究竟是几天,还是几小时,等等。
- today,tomorrow:模糊指定时间。
12.2.1、atd 服务
默认 CentOS Linux release 7.5.1804 (AltArch) 没有安装 at 的二进制包,需要手动安装
[root@arm64v8 ~]# yum install at -y
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
Package at-3.1.13-24.el7.aarch64 already installed and latest version
Nothing to do
[root@arm64v8 ~]#
如果定制了 at 计划任务,想要使其生效,必须开启 atd 服务
[root@arm64v8 ~]# systemctl start atd
[root@arm64v8 ~]# systemctl status atd
● atd.service - Job spooling tools
Loaded: loaded (/usr/lib/systemd/system/atd.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2021-08-07 14:34:24 CST; 56min ago
Main PID: 19770 (atd)
CGroup: /system.slice/atd.service
└─19770 /usr/sbin/atd -f
Aug 07 14:34:24 arm64v8 systemd[1]: Started Job spooling tools.
[root@arm64v8 ~]#
[root@arm64v8 ~]# systemctl enable atd
12.2.2、at 运行方式
at 既然是计划任务,那么应该会有任务执行的方式,并且将这些任务排进行程表中。那么产生计划任务的方式是怎么进行的?
- 事实上,我们使用 at 这个命令来产生所要运行的计划任务,并将这个计划任务以文字档的方式写入
/var/spool/at/
目录内,该任务便能等待atd
这个服务来执行。 - 为了防止黑客攻击,并不是所有人都可以使用 at 计划任务,需要一定的控制策略。
- at 先寻找
/etc/at.allow
这个文件,写在这个文件中的使用者才能使用 at ,没有在这个文件中的使用者则不能使用 at (即使没有写在at.deny
当中); - 如果
/etc/at.allow
不存在,就寻找/etc/at.deny
这个文件,若写在这个at.deny
的使用者则不能使用 at ,而没有在这个at.deny
文件中的使用者,就可以使用 at 命令了。 - 如果两个文件都不存在,那么只有 root 可以使用
at
这个命令。
- at 先寻找
12.2.3、举例说明
-
三天后的下午 5 点执行 /bin/ls
[root@arm64v8 ~]# at 5pm+3 days at> /bin/ls at>
## Ctr+D job 1 at Tue Aug 10 17:00:00 2021 [root@arm64v8 ~]# -
明天17点钟,输出时间到指定文件内
[root@arm64v8 ~]# at 17:20 tomorrow at> date > /tmp/date.log at>
job 2 at Sun Aug 8 17:20:00 2021 [root@arm64v8 ~]# -
计划任务设定后,在没有执行之前我们可以用 atq 命令来查看系统没有执行工作任务
[root@arm64v8 ~]# atq 2 Sun Aug 8 17:20:00 2021 a root 1 Tue Aug 10 17:00:00 2021 a root [root@arm64v8 ~]#
-
删除已经设置的任务
[root@arm64v8 ~]# atq 2 Sun Aug 8 17:20:00 2021 a root 1 Tue Aug 10 17:00:00 2021 a root [root@arm64v8 ~]# [root@arm64v8 ~]# atrm 1 [root@arm64v8 ~]# atrm 2
-
显示已经设置的任务内容
[root@arm64v8 ~]# at 5pm+3 days at> /bin/ls at>
job 4 at Tue Aug 10 17:00:00 2021 [root@arm64v8 ~]# [root@arm64v8 ~]# at -c 4 | tail -3 /bin/ls marcinDELIMITER3e93b3bc [root@arm64v8 ~]#