Skip to content

脚本控制

处理信号

Linux 利用信号与系统中的进程进行通信。可以通过对脚本进行编程,使其在收到特定信号时执行某些命令,从而控制 shell 脚本的操作

Linux 信号

Linux 系统和应用程序可以产生超过 30 个信号。

  • 常见的 Linux 系统信号
信号描述
1SIGHUP挂起(hang up)进程。
2SIGINT中断(interrupt)进程。
3SIGQUIT停止(stop)进程。
9SIGKILL无条件终止(terminate)进程。
15SIGTERM尽可能终止进程。
18SIGCONT继续运行停止的进程。
19SIGSTOP无条件停止,但不终止进程。
20SIGTSTP停止或暂停(pause),但不终止进程。
  • 默认情况下,bash shell 会忽略收到的任何 SIGQUIT(3) 信号和 SIGTERM(15) 信号(因此交互式 shell 才不会被意外终止)。

  • bash shell 会处理收到的所有 SIGHUP(1) 信号和 SIGINT(2) 信号。

  • 处理 SIGHUP(1) 信号

    • 如果收到了 SIGHUP(1) 信号(比如在离开交互式 shell 时),bash shell 就会退出
    • 但在退出之前,它会将 SIGHUP(1) 信号传给所有由该 shell 启动的进程,包括正在运行的 shell 脚本
  • 处理 SIGHUP(2) 信号

    • 随着收到 SIGINT(2) 信号,shell 会被中断
    • Linux 内核将不再为 shell 分配 CPU 处理时间。
    • 当出现这种情况时,shell 会将 SIGINT(2) 信号传给由其启动的所有进程,以此告知出现的状况
  • shell 会将这些信号传给 shell 脚本来处理。而 shell 脚本默认行为是忽略这些信号,因为可能不利于脚本运行

  • 避免这种情况,可以在脚本加入识别信号的代码并做相应的处理

产生信号

bash shell 允许使用键盘上的组合键来生成两种基本的 Linux 信号。这个特性在需要停止暂停失控脚本时非常方便。

中断进程

  • Ctrl+C 组合键会发送 SIGINT 信号,终止 shell 中当前运行的进程
  • 应用示例
shell
# 1.指定睡眠1800秒。(通过ctrl+c提前终止sleep命令。)
[root@localhost testdir]# sleep 1800
^C
[root@localhost testdir]#

暂停进程

  • 也可以暂停进程,而不是将其终止。
  • 它往往可以在不终止进程的情况下,使你能够深入脚本内部一窥究竟。
  • Ctrl+Z 组合键会生成 SIGTSTP 信号,停止 shell 中运行的任何进程
  • 停止stopping )进程跟终止terminating )进程不同前者让程序继续驻留在内存中,还能从上次停止的位置继续运行
  • 应用示例
shell
# 1.通过ctrl+z终止sleep进程。
[root@localhost testdir]# sleep 1800
^Z
[1]+  Stopped                 sleep 1800

# 2.通过ctrl+z终止sleep进程。(这里进程方括号中的作业号会加1。)
[root@localhost testdir]# sleep 1800
^Z
[2]+  Stopped                 sleep 1800

# 3.查看已停止的作业。(在s列,已停止作业的状态显示为T。)
[root@localhost testdir]# ps -l
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 T     0  4267 32284  0  80   0 - 27014 do_sig pts/0    00:00:00 sleep
0 T     0  4372 32284  0  80   0 - 27014 do_sig pts/0    00:00:00 sleep
0 R     0  4407 32284  0  80   0 - 38332 -      pts/0    00:00:00 ps
4 S     0 32284 32128  0  80   0 - 29170 do_wai pts/0    00:00:00 bash

# 4.可以用 kill 命令发送 SIGKILL(9) 信号将其终止。
[root@localhost testdir]# kill -9 4267
[1]-  Killed                  sleep 1800
[root@localhost testdir]# kill -9 4372
[2]+  Killed                  sleep 1800

# 5.成功停止sleep进程。
[root@localhost testdir]# ps -l
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 R     0  4641 32284  0  80   0 - 38324 -      pts/0    00:00:00 ps
4 S     0 32284 32128  0  80   0 - 29170 do_wai pts/0    00:00:00 bash
[root@localhost testdir]#
  • 注意事项有时这么做是比较危险的。(比如,脚本打开了一个关键的系统文件的文件锁。)

捕获信号

你也可以用其他命令在信号出现时将其捕获,而不是忽略信号。

  • trap 命令可以指定 shell 脚本需要侦测并拦截的 Linux 信号
  • 如果脚本收到了 trap 命令中列出的信号,则该信号不再由 shell 处理,而是由本地处理
  • trap 命令的格式如下:
shell
trap commands signals
  • trap 命令中,需要在 commands 部分列出想要 shell 执行的命令,在 signals 部分列出想要捕获的信号(多个信号之间以空格分隔)。
  • 指定信号的时候,可以使用信号的信号名
  • 应用示例
shell
# 1.脚本内容。(使用 trap 命令捕获 SIGINT 信号并控制脚本的行为。)
[root@localhost testdir]# cat test.sh 
#!/bin/bash
#
trap "echo ' ctrl+c : termination failure! '" SIGINT
#
count=1
until [ $count -gt 5 ]; do
    echo "loop - $count"
    sleep 3
    count=$((count + 1))
done
#
exit

# 2.执行脚本。(每次使用 Ctrl+C 组合键,脚本都会执行 trap 命令中指定的 echo 语句,而不是忽略信号并让 shell 停止该脚本。)
[root@localhost testdir]# ./test.sh 
loop - 1
^C ctrl+c : termination failure! 
loop - 2
^C ctrl+c : termination failure! 
loop - 3
loop - 4
loop - 5
[root@localhost testdir]#
  • 注意事项

    • 如果脚本中的命令被信号中断,使用带有指定命令的 trap 未必能让被中断的命令继续执行。
    • 为了保证脚本中的关键操作不被打断,请使用带有空操作命令trap及要捕获的信号列表
    • 例如:trap "" SIGINT
    • 这种形式的 trap 命令允许脚本完全忽略 SIGINT 信号,继续执行重要的工作

捕获脚本退出

可以在 shell 脚本退出时捕获信号。

  • 要捕获 shell 脚本的退出,只需在 trap 命令后加上 EXIT 信号即可。
  • 应用示例
shell
# 1.脚本内容。
[root@localhost testdir]# cat test.sh 
#!/bin/bash
#
trap "echo ' Goodbye... '" EXIT
#
count=1
until [ $count -gt 5 ]; do
    echo "loop - $count"
    sleep 3
    count=$((count + 1))
done
#
exit

# 2.执行脚本。(运行到正常的退出位置时,触发了 EXIT。)
[root@localhost testdir]# ./test.sh 
loop - 1
loop - 2
loop - 3
loop - 4
loop - 5
 Goodbye... 
 
 # 2.执行脚本。(提前退出脚本,则依然能捕获到 EXIT。)
[root@localhost testdir]# ./test.sh 
loop - 1
loop - 2
^C Goodbye...

修改或移除信号捕获

要想在脚本中的不同位置进行不同的信号捕获处理,只需重新使用带有新选项的 trap 命令。

  • 修改信号捕获 - 应用示例
shell
# 1.脚本内容。(增加trap)
[root@localhost testdir]# cat test.sh 
#!/bin/bash
#
trap "echo ' trap 1 '" SIGINT
#
count=1
until [ $count -gt 3 ]; do
    echo "loop[1] - $count"
    sleep 2
    count=$((count + 1))
done
#
trap "echo ' trap 2 '" SIGINT
#
count=1
until [ $count -gt 3 ]; do
    echo "loop[2] - $count"
    sleep 2
    count=$((count + 1))
done
#
exit

# 2.执行脚本。(如果信号是在捕获被修改前接收到的,则脚本仍然会根据原先的 trap 命令处理该信号。)
[root@localhost testdir]# ./test.sh 
loop[1] - 1
loop[1] - 2
^C trap 1 
loop[1] - 3
loop[2] - 1
^C trap 2 
loop[2] - 2
loop[2] - 3
  • 也可以移除已设置好的信号捕获。
  • trap 命令与希望恢复默认行为的信号列表之间加上两个连字符即可。
  • 移除信号捕获 - 应用示例
shell
# 1.脚本内容。(trap -- SIGINT 移除信号。)
[root@localhost testdir]# cat test.sh 
#!/bin/bash
#
trap "echo ' Sorry...Ctrl-C is trapped. '" SIGINT
#
count=1
until [ $count -gt 3 ]; do
    echo "loop[1] - $count"
    sleep 2
    count=$((count + 1))
done
#
trap -- SIGINT
echo "The trap is now removed."
#
count=1
until [ $count -gt 3 ]; do
    echo "loop[2] - $count"
    sleep 2
    count=$((count + 1))
done
#
exit

# 2.执行脚本。(捕获移除前,忽略终止信号;捕获移除后,按照默认处理,终止信号生效。)
[root@localhost testdir]# ./test.sh 
loop[1] - 1
loop[1] - 2
^C Sorry...Ctrl-C is trapped. 
loop[1] - 3
The trap is now removed.
loop[2] - 1
^C
[root@localhost testdir]#

以后台模式运行脚本

有些脚本可能要执行很长一段时间。此时,直接在命令行界面运行 shell 脚本就不怎么方便了。

后台运行脚本

以后台模式运行 shell 脚本非常简单,只需在脚本名后面加上 & 即可。

  • 应用示例
shell
# 1.脚本内容。
[root@localhost testdir]# cat test.sh 
#!/bin/bash
echo
count=1
until [ $count -gt 5 ]; do
    echo "loop - $count"
    sleep 2
    count=$((count + 1))
done
#
exit

# 2.执行脚本。
# 方括号中的数字(2)是 shell 分配给后台进程的作业号。
# 之后的数字(18698)是 Linux 系统为进程分配的进程 ID(PID)。
# Linux 系统中的每个进程都必须有唯一的 PID。
[root@localhost testdir]# ./test.sh &
[2] 18698
[root@localhost testdir]# 
loop - 1
loop - 2
loop - 3
loop - 4
loop - 5
^C
[2]-  Done                    ./test.sh
  • 提示:当后台进程运行时,它仍然会使用终端显示器来显示 STDOUTSTDERR 消息
  • 最好是将后台脚本的 STDOUT 和 STDERR 进行重定向避免这种杂乱的输出
shell
# 1.执行脚本。(将消息重定向,不在终端显示。)
[root@localhost testdir]# ./test.sh &> service.log
[root@localhost testdir]# 

# 2.日志记录信息。
[root@localhost testdir]# cat service.log 

loop - 1
loop - 2
loop - 3
loop - 4
loop - 5

运行多个后台作业

在使用命令行提示符的情况下,可以同时启动多个后台作业。

  • 通过 ps 命令可以看到,所有脚本都处于运行状态
shell
# 1.每次启动新作业时,Linux 系统都会为其分配新的作业号和 PID。
[root@localhost testdir]# ps
  PID TTY          TIME CMD
22493 pts/0    00:00:00 vim
27305 pts/0    00:00:00 test.sh
27381 pts/0    00:00:00 test.sh
27441 pts/0    00:00:00 sleep
27450 pts/0    00:00:00 sleep
27451 pts/0    00:00:00 ps
32284 pts/0    00:00:00 bash
  • 注意事项:在 ps 命令的输出中,每一个后台进程都和终端会话(pts/0)终端关联在一起。
  • 如果终端会话退出,那么后台进程也会随之退出。

在非控制台下运行脚本

有时候,即便退出了终端会话,你也想在终端会话中启动 shell 脚本,让脚本一直以后台模式运行到结束。

  • nohup 命令能阻断发给特定进程的 SIGHUP 信号。当退出终端会话时,这可以避免进程退出
  • nohup 命令的格式如下
shell
nohup command
  • 下面的例子使用一个后台脚本作为 command :
shell
# 1.非控制台运行脚本。(和普通后台进程一样,shell 会给 command 分配一个作业号,Linux 系统会为其分配一个 PID号。)
[root@localhost testdir]# nohup ./test.sh &
[2] 520
[root@localhost testdir]# nohup: ignoring input and appending output to ‘nohup.out’
  • 由于 nohup 命令会解除终端进程之间的关联,因此进程不再同 STDOUT 和 STDERR 绑定在一起。
  • 为了保存该命令产生的输出,nohup 命令会自动将 STDOUT 和 STDERR 产生的消息重定向到一个名为 nohup.out文件中
  • nohup.out 文件包含了原本要发送到终端显示器上的所有输出
shell
[root@localhost testdir]# cat nohup.out 

loop - 1
loop - 2
loop - 3
loop - 4
loop - 5
[root@localhost testdir]#
  • 提示:nohup.out 文件一般在当前工作目录中创建,否则会在 $HOME 目录中创建。

  • 注意事项

    • 如果使用 nohop 运行了另一个命令,那么该命令的输出会被追加到已有的 nohup.out 文件中。
    • 当运行位于同一目录中的多个命令时,一定要当心,因为所有的命令输出都会****发送到同一个**** nohup.out 文件中,结果会让人摸不着头脑

作业控制

作业控制包括启动停止、“杀死”以及恢复作业。

查看作业

jobs 是作业控制中的关键命令,该命令允许用户查看 shell 当前正在处理的作业

  • 应用示例
shell
# 1.脚本内容。
# 脚本用$$变量来显示 Linux 系统分配给该脚本的 PID。
[root@localhost testdir]# cat test.sh 
#!/bin/bash
#
echo "Script Process ID: $$"
#
count=1
until [ $count -gt 5 ]; do
    echo "loop - $count"
    sleep 10
    count=$((count + 1))
done
#
echo "End of script..."
#
exit

# 2.执行脚本。(使用 Ctrl+Z 组合键停止脚本。)
[root@localhost testdir]# ./test.sh 
Script Process ID: 11379
loop - 1
^Z
[2]+  Stopped                 ./test.sh

# 3.利用 & 将另一个作业作为后台进程启动。
[root@localhost testdir]# ./test.sh > test.out &
[3] 11585

# 4.通过 jobs 命令可以查看分配给 shell 的作业。
# jobs 命令显示了一个`已停止`的作业和一个`运行中`的作业,以及两者的作业号和作业`使用的命令`。
# 带有`加号`的作业为`默认作业`,如果作业控制命令没有指定作业号,则引用的就是该作业。
# 带有`减号`的作业会在默认作业结束之后成为`下一个默认作业`。
# 任何时候,不管 shell 中运行着多少作业,带加号的作业`只能有一个`,带减号的作业`也只能有一个`。
[root@localhost testdir]# jobs
[1]-  Stopped                 vim test.
[2]+  Stopped                 ./test.sh
[3]   Running                 ./test.sh > test.out &
  • jobs 常用命令选项
选项描述
-l列出进程的 PID 以及作业号。
-n只列出上次 shell 发出通知后状态发生改变的作业。
-p只列出作业的 PID。
-r只列出运行中的作业。
-s只列出已停止的作业。

重启已停止的作业

在 bash 作业控制中,可以将已停止的作业作为后台进程或前台进程重启

  • 前台进程接管当前使用的终端,因此在使用该特性时要小心
  • 要以后台模式重启作业,可以使用 bg 命令:
shell
# 1.脚本内容。
[root@localhost testdir]# cat test.sh 
#!/bin/bash
sleep 1800

# 2.执行脚本。(使用 Ctrl+Z 组合键停止脚本。)
[root@localhost testdir]# ./test.sh 
^Z
[1]+  Stopped                 ./test.sh

# 3.使用 bg 命令就可以将其以后台模式重启。
# 注意,当作业被转入后台模式时,并不会显示其 PID。
[root@localhost testdir]# bg
[1]+ ./test.sh &

# 4.状态变为Running,重启成功。
[root@localhost testdir]# jobs
[1]+  Running                 ./test.sh &
  • 如果存在多个作业,则需要在 bg 命令后加上作业号,以便于控制:
shell
# 1.查看当前作业。
[root@localhost testdir]# jobs
[root@localhost testdir]# 

# 2.执行脚本。(使用 Ctrl+Z 组合键停止脚本。)
[root@localhost testdir]# ./test.sh 
^Z
[1]+  Stopped                 ./test.sh
[root@localhost testdir]# 

# 3.执行脚本。(使用 Ctrl+Z 组合键停止脚本。)
[root@localhost testdir]# ./test.sh 
^Z
[2]+  Stopped                 ./test.sh
[root@localhost testdir]# 

# 4.bg 2 命令用于将第二个作业置于后台模式。
[root@localhost testdir]# bg 2
[2]+ ./test.sh &

# 5.当使用 jobs 命令时,它列出了作业及其状态,即便默认作业当前并未处于后台模式。
[root@localhost testdir]# jobs
[1]+  Stopped                 ./test.sh
[2]-  Running                 ./test.sh &
  • 要以前台模式重启作业,可以使用带有作业号的 fg 命令:
shell
# 1.查看当前作业。
[root@localhost testdir]# jobs
[1]+  Stopped                 ./test.sh
[2]-  Running                 ./test.sh &

# 2.将第二个作业置于前台模式。
[root@localhost testdir]# fg 2
./test.sh
^C
[root@localhost testdir]#

调整谦让度

在多任务操作系统(比如 Linux)中,内核负责为每个运行的进程分配 CPU 时间。

  • 调度优先级[也称为谦让度nice value )]是指内核为进程分配的 CPU 时间(相对于其他进程)。
  • 在 Linux 系统中,由 shell 启动的所有进程的调度优先级默认都是相同的
  • 调度优先级是一个整数值,取值范围从-20(最高优先级)到+19(最低优先级)。
  • 默认情况下,bash shell 以优先级 0 来启动所有进程。

nice 命令

nice 命令允许在启动命令时设置其调度优先级

  • nice 命令的 -n 选项指定新的优先级
shell
# 1.指定优先级为10。
[root@localhost testdir]# nice -n 10 ./test.sh > test.out &
[2] 15884

# 2.谦让度(NI列)已经调整到了 10。
[root@localhost testdir]# ps -p 15884 -o pid,ppid,ni,cmd
  PID  PPID  NI CMD
15884 32284  10 /bin/bash ./test.sh
  • 提示

    • nice 命令只有 root 用户或者特权用户才能提高作业的优先级
    • 即便提高其优先级的操作没有成功,指定的命令依然可以运行

renice 命令

有时候,你想修改系统中已运行命令优先级

  • renice 命令,通过指定运行进程的 PID改变其优先级
shell
# 1.启动。
[root@localhost testdir]# ./test.sh > test.out &
[1] 27793

# 2.查看当前运行中的进程谦让度(NI列)。
[root@localhost testdir]# ps -p 27793 -o pid,ppid,ni,cmd
  PID  PPID  NI CMD
27793 32284   0 /bin/bash ./test.sh

# 3.修改谦让度。
[root@localhost testdir]# renice -n 10 -p 27793
27793 (process ID) old priority 0, new priority 10

# 4.谦让度(NI列)已经调整到了 10。
[root@localhost testdir]# ps -p 27793 -o pid,ppid,ni,cmd
  PID  PPID  NI CMD
27793 32284  10 /bin/bash ./test.sh
[root@localhost testdir]#
  • nice 命令一样,renice 命令对于非特权用户也有一些限制:只能对属主为自己的进程使用 renice只能降低调度优先级
  • root 用户特权用户可以使用 renice 命令对任意进程的优先级做任意调整

定时运行作业

在使用脚本时,你也许希望脚本能在以后某个你无法亲临现场的时候运行。

使用 at 命令调度作业

at 命令允许指定 Linux 系统何时运行脚本。

  • at 的守护进程 atd 在后台运行,在作业队列中检查待运行的作业
  • atd 守护进程会检查系统的一个特殊目录( 通常位于 /var/spool/at/var/spool/cron/atjobs ),从中获取 at 命令提交的作业。
  • 默认情况下atd 守护进程每隔 60 检查一次这个目录。
  • 如果其中有作业,那么 atd 守护进程就会查看此作业的运行时间。
  • 如果时间跟当前时间一致就运行此作业。

at 命令的格式

shell
# 在默认情况下,at 命令会将 STDIN 的输入放入队列。

# 用 -f 选项指定用于从中读取命令(脚本文件)的文件名。

# time 选项指定了你希望何时运行该作业。

# 如果指定的时间已经过去,那么 at 命令会在第二天的同一时刻运行指定的作业。

at [-f filename] time
  • 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 。
  • 针对不同优先级,有 52 种作业队列。

  • 作业队列通常用小写字母 a~z 和大写字母 A~Z 来指代,A 队列和 a 队列是两个不同的队列。

  • 作业队列的字母排序越高,此队列中的作业运行优先级就越低(谦让度更大)。

  • 默认情况下at 命令提交的作业会被放入 a 队列

  • 如果想以较低的优先级运行作业,可以用 -q 选项指定其他的队列

  • 如果相较于其他进程你希望你的作业尽可能少地占用 CPU,可以将其放入 z 队列


获取作业的输出

  • 当在 Linux 系统中运行 at 命令时,显示器并不会关联到该作业。
  • Linux 系统反而会将提交该作业的用户 email 地址作为 STDOUT 和 STDERR。
  • 任何送往 STDOUT 或 STDERR 的输出都会通过邮件系统传给该用户。
  • 使用 email
shell
# 1.脚本内容。
[root@localhost testdir]# cat test.sh 
#!/bin/bash
echo "hello!"
sleep 5
echo "This is the script's end."
#
exit

# 2.at 命令会显示分配给作业的作业号以及为作业安排的运行时间。(now 指示 at 命令立刻执行该脚本。)
[root@localhost testdir]# at -f test.sh now
job 2 at Wed Jun 21 17:34:00 2023
  • at 命令通过 sendmail 应用程序发送 email。
  • 如果系统中没有安装 sendmail,那就无法获得任何输出
  • 因此在使用 at 命令时,最好脚本中对 STDOUT 和 STDERR 进行重定向
shell
# 1.脚本内容。
[root@localhost testdir]# cat test.sh 
#!/bin/bash
#
outfile=test.out
exec 3>$outfile
#
echo "hello!" >&3
echo "This is the script's end." >&3
#
exit

# 2.执行at命令。
[root@localhost testdir]# at -f test.sh now
job 6 at Wed Jun 21 17:44:00 2023

# 3.查看重定向输出。
[root@localhost testdir]# cat test.out 
hello!
This is the script's end.

6.1.3 列出等待的作业

  • atq 命令可以查看系统中有哪些作业在等待
shell
# 1.启动三个不同时间点执行的作业。
[root@localhost testdir]# at -f test.sh tomorrow
job 10 at Thu Jun 22 17:54:00 2023
[root@localhost testdir]# at -f test.sh 20:30
job 11 at Wed Jun 21 20:30:00 2023
[root@localhost testdir]# at -f test.sh now+1hour
job 12 at Wed Jun 21 18:54:00 2023
[root@localhost testdir]# 

# 2.查看哪些作业在等待。
[root@localhost testdir]# atq
11 Wed Jun 21 20:30:00 2023 a root
10 Thu Jun 22 17:54:00 2023 a root
12 Wed Jun 21 18:54:00 2023 a root
  • 作业列表中显示了作业号、系统运行该作业的日期和时间,以及该作业所在的作业队列

6.1.4 删除作业

  • 可以用 atrm 命令删除等待中的作业。
  • 应用示例
shell
# 1.查看当前等待中的作业。
[root@localhost testdir]# atq
11 Wed Jun 21 20:30:00 2023 a root
10 Thu Jun 22 17:54:00 2023 a root
12 Wed Jun 21 18:54:00 2023 a root

# 2.通过作业号进行移除。
[root@localhost testdir]# atrm 11
[root@localhost testdir]# 

# 3.剩下的等待作业。
[root@localhost testdir]# atq
10 Thu Jun 22 17:54:00 2023 a root
12 Wed Jun 21 18:54:00 2023 a root
[root@localhost testdir]#
  • 提示只能删除自己提交的作业,不能删除其他人的。

调度需要定期运行的脚本

如果需要脚本在每天、每周或每月的同一时间运行。这时候与其频繁使用 at 命令,不如利用 Linux 系统的另一个特性

  • Linux 系统使用 cron 程序调度需要定期执行的作业。
  • cron后台运行,并会检查一个特殊的表(cron 时间表),从中获知已安排执行的作业。

cron 时间表

  • cron 时间表通过一种特别的格式指定作业何时运行:
shell
minutepasthour hourofday dayofmonth month dayofweek command
  • cron 时间表允许使用特定值取值范围(比如 1~5)或者通配符(星号)来指定各个字段。
  • 如果想在每天的 10:15 运行一个命令,可以使用如下 cron 时间表字段:
shell
# dayofmonth、month 以及 dayofweek 字段中的通配符表明,cron 会在每天 10:15 执行该命令。
15 10 * * * command
  • 要指定一条在每周一的下午 4:15(4:15 p.m.)执行的命令,可以使用军事时间(1:00 p.m.是 13:00,2:00 p.m.是 14:00,3:00 p.m.是 15:00,以此类推),如下所示:
shell
15 16 * * 1 command
  • 可以使用三字符的文本值(mon、tue、wed、thu、fri、sat、sun)或数值(0 或 7 代表周日,6 代表周六)来指定 dayofweek 字段。
  • 要想在每月第一天的中午 12 执行命令,可以使用下列字段:
shell
00 12 1 * * command
  • 因为无法设置一个 dayofmonth 值,涵盖所有月份的最后一天。常用的解决方法是加一个 if-then 语句,在其中使用 date 命令检查明天的日期是不是某个月份的第一天(01):
shell
# 这行脚本会在每天中午 12 点检查当天是不是当月的最后一天(28~31),如果是,就由cron 执行 command。
00 12 28-31 * *
if [ "$(date +%d -d tomorrow)" = 01 ]; then
    command
fi
  • 命令列表必须指定要运行的命令脚本的完整路径
  • 可以像在命令行中那样,添加所需的任何选项重定向符
shell
15 10 * * * /home/christine/backup.sh > backup.out
  • 提示cron 程序会以提交作业的用户身份运行该脚本,因此你必须有访问该脚本(或命令)以及输出文件的合理权限

构建 cron 时间表

Linux 提供了 crontab 命令来处理 cron 时间表。

  • 列出已有的 cron 时间表:
shell
[root@localhost ~]# crontab -l
  • 默认情况下,用户的 cron 时间表文件并不存在
  • 可以使用 -e 选项向 cron 时间表添加字段

浏览cron 目录

  • 如果创建的脚本对于执行时间的精确性要求不高,则用预配置的 cron 脚本目录会更方便。
  • 预配置的基础目录共有 4 :hourly、daily、monthly 和 weekly:
shell
# 1.列出预配置目录。(如果你的脚本需要每天运行一次,那么将脚本复制到 daily 目录,cron 就会每天运行它。)
[root@localhost ~]# ls /etc/cron.*ly
/etc/cron.daily:
logrotate  man-db.cron  mlocate

/etc/cron.hourly:
0anacron

/etc/cron.monthly:

/etc/cron.weekly:
[root@localhost ~]#

anacron 程序

cron 程序唯一的问题是它假定 Linux 系统7×24 小时运行的。除非你的 Linux 运行在服务器环境,否则这种假设未必成立。

  • 如果某个作业在 cron 时间表中设置的运行时间已到,但这时候 Linux 系统处于关闭状态,那么该作业就不会运行
  • 再次启动系统时,cron 程序不会再去运行那些错过的作业
  • anacron 判断出某个作业错过了设置的运行时间,它会尽快运行该作业。(如果 Linux 系统关闭了几天,等到再次启动时,原计划在关机期间运行的作业会自动运行。)
  • 有了 anacron,就能确保作业一定能运行,这正是通常使用 anacron 代替 cron 调度作业的原因。
  • anacron 程序只处理位于 cron 目录的程序
  • 它通过时间戳来判断作业是否在正确的计划间隔内运行了。
  • 每个 cron 目录都有一个时间戳文件,该文件位于 /var/spool/anacron
shell
# 1.列出anacron下的文件。
[root@localhost ~]# ls /var/spool/anacron/
cron.daily  cron.monthly  cron.weekly

# 2.查看cron.daily文件内容。
[root@localhost ~]# cat /var/spool/anacron/cron.daily 
20230622
[root@localhost ~]# 

# 3.anacron 程序使用自己的时间表(通常位于/etc/anacrontab)来检查作业目录。
[root@localhost ~]# 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

#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
[root@localhost ~]#
  • anacron 时间表的基本格式和 cron 时间表略有不同
shell
# period 字段定义了作业的运行频率(以天为单位)

# delay 字段指定了在系统启动后,anacron 程序需要等待多少分钟再开始运行错过的脚本。

# identifier 字段是一个独特的非空字符串,比如 cron.weekly。它唯一的作用是标识出现在日志消息和错误 email 中的作业。

# command 字段包含了 run-parts 程序和一个 cron 脚本目录名。run-parts 程序负责运行指定目录中的所有脚本。

period delay identifier command

使用新 shell 启动脚本

如果每次用户启动新的 bash shell都能运行相关的脚本(哪怕是特定用户启动的 bash shell),那将会非常方便。

  • 当用户登录 bash shell 时要运行的启动文件,如下(基本上,以下所列文件中的第一个文件会被运行其余的则会被忽略):

    • $HOME/.bash_profile
    • $HOME/.bash_login
    • $HOME/.profile
  • 因此,应该将需要在登录时运行的脚本放在上述第一个文件中

  • 每次启动新 shell,bash shell 都会运行 .bashrc 文件,对此我们可以验证

shell
# 1.对.bashrc文件加入一条echo语句。
[root@localhost ~]# cat $HOME/.bashrc
# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Source global definitions
if [ -f /etc/bashrc ]; then
 . /etc/bashrc
fi

# ----------user defined-----------
echo "I'm in a new shell!"
[root@localhost ~]# 

# 2.启动一个新 shell。
[root@localhost ~]# bash
I'm in a new shell!
[root@localhost ~]# 

# 3.验证完成后退出。
[root@localhost ~]# exit
exit
[root@localhost ~]#