第 12 章 Linux 定时任务

作者: Brinnatt 分类: ARM64 Linux 基础精修 发布时间: 2022-01-21 12:10

12.1、配置 crond 定时任务

关于定时任务,首先需弄清的概念:

  1. crond 是一个 daemon 类程序,路径为 /usr/sbin/crond。默认会以后台方式启动,service 或 systemd 方式启动 crond 默认也是后台方式的。
  2. crondtab 是管理 crontab file 的工具,而 crontab file 是定义定时任务条目的文件。
  3. 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 就跟这有关。在最后还给出了任务条目的定义方式:

  1. 每个任务条目分为 6 段,每段以空格分隔,之所以此处多了 user-name 段是因为 /etc/crontab 为系统定时任务文件,而一般定时任务是没有该段的。
  2. 前五段为时间的设定段,分别表示"分时日月周",它们的定义不能超出合理值范围,第六段为所要执行的命令或脚本任务段。
  3. 在时间定义段中,使用 * 表示每单位,即每分钟,每小时,每天,每月,每周几(仍然是每天)。实际上,按 man 文档中解释,* 表示的是从每个时间段的起始到结尾,也就是全部时间单位的意思。例如在小时上设置 *,表示 0, 1, 2, 3 ... 22, 23 的意思。
  4. 每个时间段中,都可以使用逗号 , 来表示枚举,例如定义 0, 30, 50 * * * * 表示每个时辰的整点、第 30 分钟和第 50 分钟都执行该任务。
  5. 每个时间段中,都可以使用 - 定义范围,可以结合逗号使用。如分钟段定义了 00, 20-30, 50 表示每个时辰的整点、第 20 到 30 分钟的每分钟、第 50 分钟都执行该任务。
  6. 每个时间段中,使用 / 表示忽略时间,如在小时段定义了 0-13/2 表示在 0/2/4/6/8/10/12 点才满足时间定义。常使用 */N 表示每隔多久的意思。例如 00 */2 * * * 表示在每天每隔两小时的整点执行该任务(严格地说是 0-23/2,也就是 0,2,4,...,22,所以凌晨 1 点不会执行任务)。
  7. 如果定义的日和周冲突了,则会多次执行(不包括因为 * 号导致的冲突)。例如每月的 15 号执行该任务,同时又定义了周三执行该任务,正常无冲突情况下,将在周三和每月 15 号执行,但如果某月的 15 号同时是周三,则该任务在此日执行两次。因此,应该尽力避免同时定义周和日的任务。
  8. 命令段(即第 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 为任务定义文件。

  1. 在此文件中,空行会被忽略,首个非空白字符且以 # 开头的行为注释行,但 # 不能出现在行中。
  2. 可以在 crontab file 中设置环境变量,方式为 "name=value",等号两边的空格可随意,即 "name = value" 也是允许的。但 value 中出现的空格必须使用引号包围。
  3. 默认 crond 命令启动的时候会初始化所有变量,除了某几个变量会被 crond daemon 自动设置好,其他所有变量都被设置为空值。自动设置的变量包括 SHELL=/bin/sh,以及 HOME 和 LOGNAME(在 CentOS 上则称为 USER),后两者将被默认设置为 /etc/passwd 中指定的值。其中 SHELL 和 HOME 可以被 crontab file 中自定义的变量覆盖,但 LOGNAME 不允许覆盖。当然,自行定义的变量也会被加载到内存。
  4. 除了 LOGNAME/HOME/SHELL 变量之外,如果设置了发送邮件,则 crond 还会寻找 MAILTO 变量。如果设置了 MAILTO,则邮件将发送给此变量指定的地址,如果 MAILTO 定义的值为空(MAILTO=""),将不发送邮件,其他所有情况邮件都会发送给 crontab file 的所有者。
  5. 在系统定时任务文件 /etc/crontab 中,默认已定义 PATH 环境变量和 SHELL 环境变量,其中 PATH=/sbin:/bin:/usr/sbin:/usr/bin。
  6. 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 这个命令。

12.2.3、举例说明

  1. 三天后的下午 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 ~]#
  2. 明天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 ~]#
  3. 计划任务设定后,在没有执行之前我们可以用 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 ~]#
  4. 删除已经设置的任务

    [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
  5. 显示已经设置的任务内容

    [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 ~]#
标签云