shell脚本编程笔记(七)—— 信号处理与作业控制

16 篇文章 3 订阅

一、 处理信号

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

默认情况下, bash shell会忽略收到的SIGQUIT (3)SIGTERM (15)信号(因此交互式shell不会被意外终止),但是会处理收到的SIGHUP (1)SIGINT (2)信号。

有时候,忽略这些信号可能会不利于脚本的运行,要避免这种情况,你可以脚本中加入识别信号的代码,并执行命令来处理信号。

二、 生成信号

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

1. 中断进程

Ctrl+C组合键会生成SIGINT信号,并将其发送给当前在shell中运行的所有进程,停止shell中当前运行的进程。

2. 暂停进程

Ctrl+Z组合键会生成一个SIGTSTP信号,暂停shell中运行的任何进程,这会让程序继续保留在内存中,并能从上次停止的位置继续运行。有时这可能会比较危险(比如,脚本打开了一个关键的系统文件的文件锁),但通常它可以在不终止进程的情况下使你能够深入脚本内部一窥究竟。

$ sleep 100
^Z
[1]+ Stopped sleep 100

方括号中的数字是shell分配的作业号job number)。shell中运行的每个进程称为作业并为每个作业分配唯一的作业号。如果你的shell会话中暂停的作业,在退出shell时, bash会提醒你。

$ sleep 100
^Z
[1]+ Stopped sleep 100
$ exit
exit
There are stopped jobs.

可以用ps命令来查看已停止的作业。

$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 501 2431 2430 0 80 0 - 27118 wait pts/0 00:00:00 bash
0 T 501 2456 2431 0 80 0 - 25227 signal pts/0 00:00:00 sleep
0 R 501 2458 2431 0 80 0 - 27034 - pts/0 00:00:00 ps

S列中(进程状态),暂停作业的状态为显示为T。这说明命令要么被跟踪,要么被暂停了。

在有已暂停作业存在的情况下,如果你仍旧想退出shell,再输入一遍exit命令就行了,shell会终止改作业。或者,既然你已经知道了已停止作业的PID,就可以用kill命令
来发送一个SIGKILL信号来终止它。

kill -9 2456

三、 捕获信号

1. trap命令

也可以不忽略信号,而是在信号出现时捕获它们并执行其他命令。如果脚本收到了trap命令中列出的信号,该信号不再按默认处理,而是交由脚本处理。

trap命令的格式是:trap commands signals

这里有个简单例子,展示了如何使用trap命令来忽略SIGINT信号,并控制脚本的行为。

$ cat test1.sh
#!/bin/bash
# Testing signal trapping
trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT  # 注意这行
echo This is a test script
count=1
while [ $count -le 10 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
echo "This is the end of the test script"

本例中trap命令会在每次检测到SIGINT信号时显示一行消息,阻止用户用Ctrl+C来停止程序。

$ ./test1.sh
This is a test script
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
^C Sorry! I have trapped Ctrl-C
Loop #6
Loop #7
Loop #8
^C Sorry! I have trapped Ctrl-C
Loop #9
Loop #10
This is the end of the test script


2. 捕获脚本退出

也可以在shell脚本退出时进行捕获,这是在shell完成任务时执行命令的一种简便方法。要捕获shell脚本的退出,只要在trap命令后加上EXIT信号就行。

$ cat test2.sh
#!/bin/bash
# Trapping the script exit
trap "echo Goodbye..." EXIT   # 注意这行
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done

执行脚本,当脚本运行到正常的退出位置时,捕获就被触发,shell会执行trap中指定的命令。

$ ./test2.sh
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
Goodbye...

如果提前退出脚本,同样能够捕获到EXIT

$ ./test2.sh
Loop #1
Loop #2
Loop #3
^CGoodbye...

3. 修改或移除捕获

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

$ cat test3.sh
#!/bin/bash
# Modifying a set trap
trap "echo ' Sorry... Ctrl-C is trapped.'" SIGINT  # 注意这个

count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done

trap "echo ' I modified the trap!'" SIGINT  # 注意这个,修改捕获SIGINT时执行命令
count=1
while [ $count -le 5 ]
do
echo "Second Loop #$count"
sleep 1
count=$[ $count + 1 ]
done

执行脚本

$ ./test3.sh
Loop #1
Loop #2
Loop #3
^C Sorry... Ctrl-C is trapped.
Loop #4
Loop #5
Second Loop #1
Second Loop #2
^C I modified the trap!
Second Loop #3
Second Loop #4
Second Loop #5

也可以删除已设置好的捕获,在trap命令与希望恢复默认行为的信号列表之间加上一个或两个破折号均可。

$ cat test3b.sh
#!/bin/bash
# Removing a set trap
trap "echo ' Sorry... Ctrl-C is trapped.'" SIGINT

count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done

# Remove the trap
trap -- SIGINT     # 或者 trap - SIGINT
echo "I just removed the trap"

count=1
while [ $count -le 5 ]
do
echo "Second Loop #$count"
sleep 1
count=$[ $count + 1 ]
done

执行脚本。移除信号捕获后,脚本按照默认行为来处理SIGINT信号,也就是终止脚本运行。但如果信号是在捕获被移除前接收到的,那么脚本会按照原先trap命令中的设置进行处理。

$ ./test3b.sh
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
I just removed the trap
Second Loop #1
Second Loop #2
Second Loop #3
^C

四、 作业控制与jobs命令

启动、停止、终止以及恢复作业的这些功能统称为作业控制,作业控制中的关键命令是jobs命令。

1. 查看作业

jobs命令允许查看shell当前正在处理的作业。

jobs
#输出结果,一个job被暂停,另一个正在运行
[1]+ Stopped ./test10.sh
[2]- Running ./test10.sh > test10.out &

要想查看作业的PID,可以在jobs命令中加入-l选项(小写的L)。

jobs -l
[1]+ 1897 Stopped ./test10.sh
[2]- 1917 Running ./test10.sh > test10.out &

你可能注意到了jobs命令输出中的加号和减号。带加号的作业会被当做默认作业。在使用作业控制命令时,如果未在命令行指定任何作业号,该作业会被当成作业控制命令的操作对象。当前的默认作业完成处理后,带减号的作业成为下一个默认作业。任何时候都只有一个带加号的作业和一个带减号的作业,不管shell中有多少个正在运行的作业。

下面例子说明了队列中的下一个作业在默认作业移除时是如何成为默认作业的。有3个独立的进程在后台被启动,默认进程(带+)是最后启动的进程,也就是3号作业。

$ ./test10.sh > test10a.out &
[1] 1950
$ ./test10.sh > test10b.out &
[2] 1952
$ ./test10.sh > test10c.out &
[3] 1955

$ jobs -l
[1] 1950 Running ./test10.sh > test10a.out &
[2]- 1952 Running ./test10.sh > test10b.out &
[3]+ 1955 Running ./test10.sh > test10c.out &

调用kill命令杀掉当前默认作业。在接下来的jobs命令输出中,先前带有减号的作业2成了现在的默认作业,作业1则带上了减号。

$ kill 1955
[3]+ Terminated ./test10.sh > test10c.out

$ jobs -l
[1]- 1950 Running ./test10.sh > test10a.out &
[2]+ 1952 Running ./test10.sh > test10b.out &

$ kill 1952
[2]+ Terminated ./test10.sh > test10b.out

$ jobs -l
[1]+ 1950 Running ./test10.sh > test10a.out &

2. 重启暂停的作业

要以后台模式重启一个作业,可用bg命令加上作业号。

$ ./test11.sh
^Z
[1]+ Stopped ./test11.sh

$ ./test12.sh
^Z
[2]+ Stopped ./test12.sh

$ bg 2
[2]+ ./test12.sh &

$ jobs
[1]+ Stopped ./test11.sh
[2]- Running ./test12.sh &

如果是默认作业(+),只需要使用bg命令就可以将其以后台模式重启。

$ ./test11.sh
^Z
[1]+ Stopped ./test11.sh
$
$ bg
[1]+ ./test11.sh &
$
$ jobs
[1]+ Running ./test11.sh &

要以前台模式重启作业,可用带有作业号的fg命令。由于作业是以前台模式运行的,直到该作业完成后,命令行界面的提示符才会出现。

$ fg 2
./test12.sh
This is the script's end...


五、 调整谦让度

在Linux系统中,调度优先级是个整数值,从-20(最高)到+19(最低),bash shell默认均以优先级0来启动进程。谦让度对应的命令是nice,你人越nice(nice值越高)越容易被欺负,优先级越低。

1. nice 命令

nice -n 命令允许设置命令启动时的调度优先级。

$ nice -n 10 ./test4.sh > test4.out &  #调低优先级
[1] 4973

$ ps -p 4973 -o pid,ppid,ni,cmd
PID PPID NI CMD
4973 4721 10 /bin/bash ./test4.sh

注意普通用户不能提高命令的优先级,否则会报错。指定的作业的确运行了,但是试图使用nice命令提高其优先级的操作却失败了。

$ nice -n -10 ./test4.sh > test4.out &
[1] 4985
$ nice: cannot set niceness: Permission denied
[1]+ Done nice -n -10 ./test4.sh > test4.out

-n选项并不是必须的,只需要在破折号后面跟上优先级也可以。

$ nice -10 ./test4.sh > test4.out &
[1] 4993

$ ps -p 4993 -o pid,ppid,ni,cmd
PID PPID NI CMD
4993 4721 10 /bin/bash ./test4.sh


2. renice 命令

renice命令可以指定运行进程的PID来改变它的优先级。

$ ./test11.sh &
[1] 5055

$ ps -p 5055 -o pid,ppid,ni,cmd
PID PPID NI CMD
5055 4721 0 /bin/bash ./test11.sh

$ renice -n 10 -p 5055
5055: old priority 0, new priority 10

$ ps -p 5055 -o pid,ppid,ni,cmd
PID PPID NI CMD
5055 4721 10 /bin/bash ./test11.sh

renice命令对普通用户也有一些限制:

  • 只能对属于你的进程执行renice
  • 只能通过renice降低进程的优先级;

root用户可以通过renice来任意调整进程的优先级。如果想完全控制运行进程,必须以root账户身份登录或使用sudo命令。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值