【Linux 命令行与 shell 脚本编程大全】 16 控制脚
导览
- Linux 可以利用信号控制脚本,这在第 4 章中已经有介绍
-
trap
命令可以在脚本中拦截 shell 发送到脚本的信号,并进行本地化操作 - 可以在脚本的执行语句最后添加
&
符号,来实现脚本的后台运行- 但这种后台运行不是静默的,任何没有重定向的 STDOUT 或 STDERR 都会输出到显示器
- 同时如果当前 shell 退出,处于该模式下运行的脚本也会退出
- 可以在脚本的执行语句最前添加
nohup
以及最后添加&
符号,来实现脚本的静默后台运行- 在这种模式下运行的脚本,任何没有重定向的 STDOUT 或 STDERR 都会被输出到当前目录下的 nohup.out 文件中
- 同时即使当前 shell 退出,处于该模式下运行的脚本也不会直接退出
-
jobs
命令可以查看当前 shell 的作业列表,$$
变量显示分配给该脚本的PID -
bg
命令可以将暂停的作业恢复后台运行 -
fg
命令可以将暂停的作业恢复前台运行 -
nice
命令可以在脚本执行时设置脚本的调度优先级 -
renice
命令可以在脚本执行过程中设置脚本的调度优先级 -
at
命令可以在预设的时间节点执行脚本 -
atq
命令可以查看当前 shell 等待执行的脚本列表 -
atrm
命令可以从等待执行的脚本列表中删除指定预设 -
cron
程序提供了定期运行脚本的接口
16.1 处理信号
16.1.1 重温 Linux 信号
- Linux 和程序之间可以生成超过 30 个信号
- 在前文的 4.1.3.1 Linux 中进程通信方式 中已经介绍过几种常见的 Linux 信号,如下图
- 默认情况下,shell 会忽略 QUIT ( 3 ) 和 TERM ( 15 ) 信号,因为只有这样,交互式的 shell 才不会意外终止
- shell 会处理 HUP ( 1 ) 和 INT ( 2 ) 信号,并将这些信号发送给脚本,但脚本的默认行为是忽略这些信号
-
要避免这种情况,可以在脚本中加入识别信号的代码,并执行命令对信号进行处理
image
-
16.1.2 生成信号
- 这里的生成信号指的是利用键盘按下组合键来向 shell 发送信号
16.1.2.1 Ctrl + C 中断进程
- Ctrl + C 会生成 INT ( 2 ) 信号,并发送给当前在 shell 中运行的进程
- 通过在执行一段命令或脚本时,如果想中途退出,按下 Ctrl + C 即可
- 但这对 vim 无效
16.1.2.2 Ctrl + Z 暂停进程
- Ctrl + Z 会生成 TSTP ( 18 ) 信号,暂停当前在 shell 中运行的进程,但继续保留在内存中
- 注意,这里的暂停不是终止进程,暂停的程序会继续保留在内存中,而且能从上次暂停的位置继续运行
- 可以做一个简单的尝试,来看一下两个组合键触发的信号有什么区别,如下图
-
首先是使用 Ctrl + C 中断进程,之后使用
ps
命令查看当前进程,并没有找到该进程,说明进程确实不存在了 -
然后是使用 Ctrl + Z 暂停进程,shell 直接输出了一句提示信息,方括号中的数字是该进程的作业号
-
之后使用
ps -l
命令查看当前进程的详细信息,发现该进程还存在,并且在列表的 S 列显示的字母是 T ,这表示该进程被暂停了 -
想要终止该进程,只需要使用
imagekill -9 PID
即可( 下图中该进程的 PID 是 25916 )
-
16.1.3 trap 捕获信号
- 使用
trap
命令可以在脚本中捕获 shell 发送给脚本的信号 - 如果脚本收到了通过
trap
命令指定的信号,那么该信号就不再由 shell 处理,而是由脚本进行本地处理 -
trap
命令的完整语法是trap commands signals
,具体操作如下图-
commands
是捕获到对应信号想要执行的命令 -
singals
是对应的信号值,可以使用信号对应的数值、名称( 可以是全称、简称 )- 比如 Ctrl + C 对应的终止信号,数值是 2 ,全称是 SIGINT ,简称是 INT ,这三个值都可以捕获到该信号
-
[ttxie@41 part16]$ cat trap-signal.sh
#!/bin/bash
trap "echo 'sorry! I have trapped Ctrl-C'" SIGINT #单引号可有可无
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
image.png
16.1.4 捕获脚本退出
- 使用
trap
命令时,在最后的信号处添加 EXIT 就可以捕获到脚本的退出信号,如下图- 可以看到, 第一次运行时,让脚本运行结束后正常退出,成功触发了 EXIT 信号,并输出了语句
- 第二次运行时,使用 Ctrl + C 提前退出脚本,也成功触发了 EXIT 信号,并输出了语句
- 第三次运行时,使用 Ctrl + Z 提前暂停脚本,并没有触发 EXIT 信号,这也说明 暂停和终止是不同的
[ttxie@41 part16]$ cat trap-exit.sh
#!/bin/bash
trap "echo Goodbye" EXIT
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
image.png
16.1.5 修改或移除捕获
16.1.5.1 修改捕获
- 修改捕获只需要在脚本的另一处对相同的信号输出不同的命令即可,如下图
[ttxie@41 part16]$ cat trap-modify-signal.sh
#!/bin/bash
trap "echo 'sorry, Ctrl-C is tapped.'" INT
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
trap "echo 'I modified the trap.'" INT
count=1
while [ $count -le 5 ]
do
echo "Second Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
image.png
16.1.5.2 移除捕获
- 移除捕获只需要在
commands
的位置使用 双破折号( -- ) 代替即可,如下图- 可以看到,当移除捕获后,按下 Ctrl + C 可以直接退出,没有任何提示
[ttxie@41 part16]$ cat trap-del-signal.sh
#!/bin/bash
trap "echo 'sorry, Ctrl-C is tapped.'" INT
count=1
while [ $count -le 3 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
trap -- INT
#
count=1
while [ $count -le 3 ]
do
echo "Second Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
image.png
16.2 以后台模式运行脚本
16.2.1 后台运行脚本
-
在脚本运行的命令后添加一个 & 符号即可实现脚本的后台运行,如下图
- 可以看到,脚本在后台运行时,首先会输出一个带作业号的以及 PID 的进程信息,运行结束后,还会再输出一个运行结束的提示信息
-
但需要注意到的是,由于在脚本内部的输出语句没有重定向,所以对应的语句还是直接输出到了显示器,这种体验不太友好
-
建议对需要后台运行的脚本,将内部的输出语句,不论是 STDOUT ,还是 STDERR ,都应该重定向到对应文件
image
16.2.2 运行多个后台作业
-
其实就是多次执行后台运行的脚本,让这些脚本能够在后台同时运行,如下图
image
16.3 nohup 在非控制台下运行脚本
-
当我们使用
&
符号对脚本后台运行时,虽然脚本的运行已经不占用 shell 的交互,但如果此时我们想要退出 shell ,首先 shell 会给予我们当前仍有脚本在运行的提示,如果再次执行退出,则会退出当前 shell ,并终止脚本的运行,如下图-
下图中,如果在脚本运行结束之前再执行一次退出,shell 窗口就会直接关闭,并且对应的脚本运行也会直接终止
image
-
-
想要让脚本实现完全的静默后台运行,可以脚本运行的语句最前面添加
nohup
命令,当然后最后依旧需要添加&
符号,如下图-
为什么说是静默后台运行,看下图便知,在当前运行的脚本中,我们并没有对输出语句进行重定向
-
但是脚本在运行时,并没有在显示器输出任何语句,对应的输出语句都被直接保存到了当前目录临时生成的 nohup.out 文件中
-
这个文件是由
imagenohup
命令自动生成的
-
-
在
nouhup
模式下,就算我们退出了当前 shell ,通过nohup
运行的脚本依旧会正常运行,并且将所有的输出语句保存到 nohup.out 中 -
需要注意的是,nohup.out 文件在同一目录下是通用的,如果在同一目录中有多个使用
nohup
运行的脚本,那么他们的输出语句都被被保存到当前目录下的同一个 nohup.out 中
16.4 作业控制
- 作业控制 是指对脚本作业的启动、暂停、终止以及恢复
16.4.1 jobs 查看作业
- 使用
jobs
命令可以查看 shell 当前正在处理的作业,如下图- 在
jobs
命令后添加-l
指令,可以看到更详细的包括作业 PID 的信息 -
$$
变量显示系统分配给该脚本的PID
- 在
[ttxie@41 part16]$ cat try-multi-bg-run.sh
#!/bin/bash
echo "Script Process ID: $$"
count=1
while [ $count -le 5 ]
do
echo "Loop, #$count"
sleep 1
count=$[ $count + 1 ]
done
echo "End of script..."
image.png
-
更多
imagejobs
命令的指令可以参考下图 -
当同时运行多个脚本时,使用
jobs
命令就可以看到多个作业,但不同的作业在列表中显示存在一定差异,如下图-
带加号的作业是 默认作业 ,如果在使用相关作业控制命令时,没有指定任何作业号,那么该作业就会被作为缺省作业号直接使用
- 例如后续会介绍到的
fg
命令,如果在下图的环境中直接使用的话,那么就会将这个作业号为 3 的默认作业切换到前台模式运行
- 例如后续会介绍到的
-
在当前 默认作业 运行结束后,带减号的作业就会变成下一个 默认作业
-
任何使用都只会同时存在一个带加号和一个带减号的作业
image
-
16.4.2 重启暂停的作业
16.4.2.1 bg 实现以后台模式重启作业
- 使用
bg
命令可以将暂停的作业以后台模式继续运行,如下图-
当直接运行脚本后并使用 Ctrl + Z 暂停脚本
-
使用
jobs -l
命令可以查看到该脚本处于暂停状态( 我运行的环境是 Mac 下的终端,暂停标识是 suspended ,Linux 中一般是 stopped ) -
直接使用
bg
命令,不添加任何作业号( 因为该作业是默认作业 ),就可以将该作业重启 -
再次使用
imagejobs -l
命令可以看到该作业已经恢复运行
-
16.4.2.2 fg 实现以前台模式重启作业
- 使用
fg
命令可以将暂停的作业以前台模式继续运行,如下图-
由于作业以前台模式恢复运行,新的命令提示符,必须等脚本运行结束后才会出现
image
-
16.5 设置调度优先级( Scheduling Priority )
- TIP :这里的原文翻译是调整谦让度,我觉得有点拽词了,没必要
- 调度优先级 是 Linux 内核分配给进程的 CPU 时间,由 shell 启动的所有进程的 调度优先级 默认都是相同的
- 调度优先级 是个整数值,范围是从 -20 ~ +19 ,-20 是最高优先级,-19 是最低优先级,默认值是 0
16.5.1 nice 设置调度优先级
-
使用
nice -n
命令可以设置命令启动时的调度优先级,如下图-
ps
命令的列表信息中默认是没有显示调度优先级的,但是可以通过-o ni
来开启该值的显示,为了防止只显示 NI 列,所以最后使用-o pid,ni,cmd
来丰富列表信息 -
可以看到,使用
imagenice -n 10
命令启动的脚本 NI 列的值就是 10
-
-
脚本启动时,默认的调度优先级是 0 ,小于 0 是调高调度优先级,大于 0 是调低调度优先级
-
但普通用户其实是无法将调度优先级调低的,如下图
-
当非 root 用户试图以 -10 的调度优先级来运行脚本时,系统给予了权限不足的提示
-
使用
jobs -l
命令可以查看到该脚本仍然是运行成功的 -
但是使用
imageps
命令查看时,发现该脚本虽然运行成功,但调度优先级是默认的,说明调度优先级的修改是失败的
-
-
如果想要更简单的使用
nice
命令,可以忽略-n
指令,直接使用nice -10
就表示将调度优先级修改到 10
16.5.2 renice 设置调度优先级
-
使用
renice
命令可以通过指定 PID ,来设置正在运行脚本的调度优先级,如下图-
可以看到,一开始通过
nice -10
启动的脚本,调度优先级是 10 -
之后通过
imagerenice -n 15
重新将脚本的调度优先级设置到 15 也是成功的
-
-
普通用户在使用
renice
命令设置脚本的调度优先级时,需要注意以下几点- 只能对属于自己的进程执行
renice
- 只能使用
renice
降低进程的调度优先级
- 只能对属于自己的进程执行
-
只有 root 用户可以使用
renice
所以设置进程的调度优先级
16.5.2.1 renice 的简写和 nice 不一样
- 需要注意以下的是,
nice
命令可以省略-n
指令直接使用nice -10
表示将脚本的调度优先级设置到 10 - 但是
renice
命令不一样,如果省略-n
指令直接使用renice -15
表示将脚本的调度优先级设置到 -15 ,如下图-
如果要使用简写形式将脚本的调度优先级设置到 15 ,应该使用
imagerenice 15
-
16.6 定时运行作业
16.6.1 用 at 命令来计划执行作业
- 使用
at
命令会将作业提交到队列中,告知 shell 何时运行该作业- 针对不同的优先级,存在 26 种不同的作业队列,通常用 a-z 和 A-Z 来指代
- 作业队列的字母排序越高,作业运行的优先级就越高
- 默认情况下,作业会被提交到 a 作业队列
- 使用
-q
指令可以显式的指定想要的队列
-
at
命令的守护进程 atd 会以后台模式运行,检查作业队列来运行作业- 大多数 Linux 会在启动时运行该守护进程
- 默认情况下,atd 进程会每隔 60 秒检查 /var/spool/at 目录,来获取
at
命令提交的定时作业 - 如果检查到当前时间需要运行的作业,就会运行该作业
16.6.1.1 at 命令的格式
-
at
命令的基本语法是at -f filename time
-
time
支持的时间格式非常灵活,比如:- 标准的小时、分钟格式,10:15
- 增加指示符,10:15 PM
- 特定时间,now 、noon 、midnight 、teatime
- 标准日期格式,MMDDYY 、MM/DD/YY 、DD.MM.YY
- 文本日期,Jul 4 、Dec 25
16.6.1.2 获取作业的输出
- 默认情况下,Linux 会将提交作业的用户对应的邮件地址作为作业 STDOUT 和 STDERR 的输出对象
- 当脚本运行时,会将脚本的输出结果发送到用户的邮件中
- 这听起来就让人觉得体验非常糟糕,而且如果当前 Linux 没有安装 sendmail 程序,就无法获取到任何输出结果
- 所以推荐将需要定时运行的脚本内部所有的输出都提前重定向到对应的文件中
- 如果不想获取任何输出,可以使用
-M
指令进行输出屏蔽 - 写一个例子演示一下,如下图
[ttxie@41 part16]$ cat at-test.sh
#!/bin/bash
sleep 5
echo "This is the script's end..." >> at-resulti.out
image.png
16.6.1.3 atq 列出等待的作业
- 使用
atq
命令可以查看当前有哪些作业在等待执行,如下图
image.png
16.6.1.4 atrm 删除作业
- 使用
atrm
命令可以删除指定作业号的作业,如下图
16.6.1.5 作业队列的权限
-
普通用户只能看到自己的作业队列,也只能删除自己的作业,root 用户可以看到所有的,也能删除所有作业,如下图
image
16.6.2 cron 安排需要定期执行的脚本
16.6.2.1 cron 时间表
- cron 程序 会在后台运行并检查 cron 时间表 ,用来获取已经安排执行的作业
-
cron 时间表 的格式是
min hour dayofmonth month dayofweek command
- 格式中的时间节点可以使用具体数字,也可以使用星号作为占位符,例如
15 10 * * * command
就是在每天的 10:15 执行某个命令 -
dayofmonth
是指一个月的哪一天,取值 1 - 31 -
dayofweek
是指一周的哪一天,取值 0 - 6 ,或者mon / tue / wed / thu / fri / sat / sun
- 格式中的时间节点可以使用具体数字,也可以使用星号作为占位符,例如
16.6.2.2 crontab 构建 cron 时间表
-
每个用户都可以用自己的 cron 时间表 来运行安排好的任务
-
使用
image imagecrontab -e
可以打开 cron 时间表 的内容编辑器,在其中按照格式编写脚本的运行时间即可,编写好之后输入:wq
保存并退出即可,如下图 -
使用
imagecrontab -l
可以查看已经规划的 cron 时间表 ,如下图
16.6.2.3 浏览 cron 目录
- 如果对脚本的执行时间要求不高,用预配置哈偶的 cron 脚本目录会更方便,如下图
-
cron.hourly 每小时执行,cron.daily 每天执行,cron.weekly 每周执行,cron.monthly 每月执行
-
将需要执行的脚本复制到以下对应的目录即可
image
-
16.6.2.4 anacron 程序
-
cron 程序 的定期基于一个假设:当前 Linux 的系统是 7 * 24 小时不间断运行的
-
那么如果当前 Linux 并不是这样运行,而是会在某个时间节点关机,之后再开机,那么本来在这个时间段应该定期执行的脚本就会被错过
-
为了解决这个问题,Linux 提供了 anacron 程序
-
如果在 Linux 再次开机时, anacron 知道某个作业错过了执行时间,就会尽快执行该作业
-
anacron 程序只会处理位于 cron 目录的脚本
-
anacron 用时间戳来决定作业是否在正确的计划间隔内执行了,如下图
-
每个 cron 目录都有自己的时间戳文件,位于 /var/spool/anacron
-
anacron 也有自己的时间表来检查作业目录,位于 /etc/anacrontab
image
-
-
anacron 时间表的格式是
period delay identifier command
-
period
用于定义作业多久运行一次,单位是天 -
delay
会指定系统启动后需要等待多长时间再开始运行错过的脚本,单位是分钟 -
identifier
是特别的非空字符串,我也不知道怎么解释 -
command
包含 run-pars 程序和 cron 脚本的目录名- run-parts 是负责运行目录中脚本的程序
-
16.6.3 使用新 shell 启动脚本
-
如果想要在每次开启新 shell 时就立即执行一段脚本,既不需要使用
at
命令,也不需要使用 cron 程序 -
回顾 6.6.1 登录式 shell 的内容,我们可以知道 shell 在开启时会首先寻找用户自定义启动文件
-
在用户自定义启动文件中一定会被执行的配置文件是 .bashrc 文件
-
所以只需要将想要执行的脚本放置在该文件中即可,如下图
image
转载来自:
作者:asing1elife
链接:https://www.jianshu.com/p/d4220628b111
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。