shell脚本之信号的捕捉
trap,翻译过来就是陷阱的意思,shell脚本中的陷阱是专门用来捕捉信号的。啥信号呢?比如经常使用的kill -9,kill -15,CTRL+C等都属于信号
1、查看所有可用的信号
trap -l或kill -l即可
[root@linux1 ~]# kill -l
63) SIGRTMAX-1 64) SIGRTMAX
[root@linux1 ~]# trap -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
......
2、常见的信号如下:
Signal Value Comment
─────────────────────────────
SIGHUP 1 终止进程,特别是终端退出时,此终端内的进程都将被终止
SIGINT 2 中断进程,几乎等同于sigterm,会尽可能的释放执行clean-up,释放资源,保存状态等(CTRL+C)
SIGQUIT 3 从键盘发出杀死(终止)进程的信号
SIGKILL 9 强制杀死进程,该信号不可被捕捉和忽略,进程收到该信号后不会执行任何clean-up行为,所以资源不会释放,状态不会保存
SIGTERM 15 杀死(终止)进程,几乎等同于sigint信号,会尽可能的释放执行clean-up,释放资源,保存状态等
SIGSTOP 19 该信号是不可被捕捉和忽略的进程停止信息,收到信号后会进入stopped状态
SIGTSTP 20 该信号是可被忽略的进程停止信号(CTRL+Z)
真正的信号名字不是SIGXXX,而是去除SIG后的单词,每个信号还有对应的代号
比如向PID为12345的进程发起1信号
kill -1 12345
kill -HUB 12345
kill -SIGHUB 12345
3、trap的选项
trap -l列出当前系统支持的信号列表,上面已经使用过,根kill -l一样
trap -p等价于trap,查看shell已经布置好的陷阱
可以看到shell默认有三个陷阱,表示忽略20,21,22信号
[root@linux1 ~]# trap
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
4、陷阱捕捉到信号后干嘛
- 忽略信号
- 捕捉到信号后做相应的处理。主要是清理一些脚本创建的临时文件,然后退出。
5、设置一个可以忽略CTRL+C和15信号的陷阱
CTRL信号对应的是SIGINT 15信号对应的是SIGTERM
[root@linux1 ~]# trap '' SIGINT SIGTERM
[root@linux1 ~]# trap
trap -- '' SIGINT
trap -- '' SIGTERM
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
这样,当前shell就不能被kill -15杀死
6、设置一个陷阱,捕捉到-15信号时,就打印“我抓到你啦~”
[root@linux1 ~]# trap 'echo "我抓到你啦~"' TERM
[root@linux1 ~]# trap
trap -- '' SIGINT
trap -- 'echo "我抓到你啦~"' SIGTERM
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
效果,当我对当前bash发起kill -15信号时就打印出来了
[root@linux1 ~]# echo $$
8827
[root@linux1 ~]# kill -15 8827
我抓到你啦~
[root@linux1 ~]# kill -15 8827
我抓到你啦~
[root@linux1 ~]# kill -15 8827
我抓到你啦~
7、在脚本中设置一个能忽略CTRL+C和CTRL+Z信号的脚本
CTRL+C是2信号,即SIGINT
CTRL+Z是20信号,即SIGTSTP
脚本:
脚本沉睡10s,然后打印success,脚本忽略INT和TSTP信号
[root@linux1 ~]# cat trap.sh
#!/bin/bash
trap '' SIGINT SIGTSTP
sleep 10
echo success
效果:
CTRL+C也不能阻止我睡觉
[root@linux1 ~]# bash trap.sh
^C^C^Z^Z^C^C^Z^Zccc^Z^Z^Z^C^C^C
success
8、布置一个当脚本被终端时能清理垃圾并立即退出脚本的陷阱
脚本如下:
[root@linux1 ~]# cat trap1.sh
#!/bin/bash
trap 'echo trap handing...;rm -rf /tmp/$BASHPID;echo TEMP files cleaned;exit' SIGINT SIGTERM SIGQUIT SIGHUP
mkdir -p /tmp/$$/
touch /tmp/$$/{a..c}.txt
sleep 10
echo first sleep success
sleep 10
echo second sleep success
这样,脚本除了SIGKILL信号(kill -9),总能清理掉临时垃圾
效果
刚开始一直不能终止,后来执行了下trap发现前面shell自己设置了一个忽略CTRL+C的陷阱,退出shell重进即可
[root@linux1 ~]# bash trap1.sh
^Ctrap handing...
TEMP files cleaned
9、陷阱的守护对象
陷阱的守护对象是shell进程本身,不会守护shell环境内的子进程。但如果是信号忽略型陷阱,则会守护整个shell进程组使其忽略给定信号。
[root@linux1 ~]# cat trap2.sh
#!/bin/bash
trap 'echo trap_handle_time: $(date +"%F %T")' SIGINT SIGTERM
echo time_start: $(date +"%F %T")
sleep 10
echo time_end1: $(date +"%F %T")
sleep 10
echo time_end2: $(date +"%F %T")
#执行脚本后,新开终端使用kill -15杀死它
[root@linux1 ~]# killall -s SIGTERM trap2.sh
#查看输出情况
[root@linux1 ~]# ./trap2.sh
time_start: 2019-08-27 10:43:48
trap_handle_time: 2019-08-27 10:43:58
time_end1: 2019-08-27 10:43:58
time_end2: 2019-08-27 10:44:08
可以发现,kill执行完后,屏幕没有立即打印trap_handle,而是等sleep 10运行完后才打印的。sleep进程都被忽略型trap守护了
只要是向shell进程发送的信号,都会等待当前正在运行的命令结束后才处理信号,然后继续脚本向下运行。(实际上,只有当shell脚本中正在执行的操作是信号安全的系统调用时,才会出现信号无法中断进程的情况,而在shell下的各种命令,我们是没法直接知道哪些命令中正在执行的系统调用是系统调用的)。
但sleep命令发起的sleep()调用,是一个信号安全的,所以上面脚本中执行sleep的过程中,信号不会直接中断它们的运行,而是等待它运行完之后再执行信号处理命令。
10、如果shell中针对某信号设置了陷阱,则该shell进程接收到该信号时,会等待其内正在运行的命令结束才开始处理陷阱
11、CTRL+C和SIGINT不是等价的。当某一时刻按下CTRL+C,它是在向整个当前运行的进程组发送SIGINT信号。对shell脚本来说,SIGINT不仅发送给shell脚本进程,还发送给脚本中当前正在运行的进程
[root@linux1 ~]# cat trap2.sh
#!/bin/bash
trap 'echo trap_handle_time: $(date +"%F %T")' SIGINT SIGTERM
echo time_start: $(date +"%F %T")
sleep 10
echo time_end1: $(date +"%F %T")
sleep 10
echo time_end2: $(date +"%F %T")
#执行脚本后,立马CTRL+C
[root@linux1 ~]# bash trap2.sh
time_start: 2019-08-27 10:20:53
^Ctrap_handle_time: 2019-08-27 10:20:54
time_end1: 2019-08-27 10:20:54
time_end2: 2019-08-27 10:21:04
可以发现,CTRL+C后,不仅trap进行处理了,sleep也立马结束了;说明CTRL+C不仅让脚本进程收到了SIGINT信号,也让当前进程收到了SIGINT信号
有点难理解,以后再来看