注意点(1)(2)(3)很重要,因为搞清楚了它们,才能明白脚本中当前正在运行的进程是先完成还是立即结束,这在写复杂脚本或任务型脚本极其重要。例如大量文档中需要替换成,假如使用sed进行处理,我们肯定不希望替换了一部分文件的时候被临时终止。
(4).每个陷阱都有守护范围。每一个陷阱只将守护它后面的所有进程,直到遇到下一个相同信号的陷阱。
以shell脚本为例,如下图所示。
(5).当shell环境下设置了信号忽略陷阱时,子shell在启动时将继承该陷阱,且这些信号忽略陷阱不可再改变或重置。信号忽略陷阱是子shell唯一继承的陷阱类型。
先在当前shell环境下设置一个忽略SIGINT的陷阱,和一个不忽略SIGTERM的陷阱。
[root@linuxidc ~]# trap '' SIGINT [root@linuxidc ~]# trap 'echo haha' SIGTERM
以下是测试脚本。脚本中首先输出脚本刚启动时的最初陷阱列表,随后修改陷阱并输出新的陷阱列表,最后重置陷阱并输出重置后的陷阱列表。
[root@linuxidc ~]# cat trap6.sh #!/bin/bash # script_name: trap6.sh echo old_trap:-------- trap -p trap 'echo haha' SIGINT SIGTERM echo new_trap:-------- trap -p echo "reset trap:------" trap - SIGINT SIGTERM trap -p
执行结果如下。
[root@linuxidc ~]# ./trap6.sh old_trap:-------- trap -- '' SIGINT new_trap:-------- trap -- '' SIGINT trap -- 'echo haha' SIGTERM reset trap:------ trap -- '' SIGINT
从结果中可以看出,启动脚本时,父shell中忽略SIGINT的陷阱被继承了,但不忽略信号的陷阱未被继承。而且脚本继承的信号忽略陷阱无法被修改和重置。
(6).交互式的shell下,如果没有定义任何SIGTERM信号的陷阱,则会忽略该信号。
所以,在默认(未定义SIGTERM陷阱)时,无法直接通过15信号杀死当前bash进程。
[root@linuxidc ~]# kill $BASHPID;echo passed;kill -9 $BASHPID passed # 此处当前bash已被kill -9强制杀死
(7).除了kill -l或trap -l列出的信号列表,trap还有4种特殊的信号:EXIT(或信号代码0)、ERR、DEBUG和RETURN。DEBUG和RETURN这两种信号陷阱无需关注。
EXIT信号也是0信号,当设置了EXIT陷阱时,每次exit的时候都会被捕捉,并做相关处理。
ERR陷阱是在设置了"set -e"时生效的,当设置了"set -e"选项,每次遇到非0退出状态码时会退出当前shell,如果写在脚本中,就是退出脚本。有了它就不用再在脚本中书写对"$?"是否(不)等于0的判断语句,不过它主要用于避免脚本中产生错误时,错误被滚雪球式的不断放大。很多人将这一设置当作写shell脚本的一项行为规范,但我个人不完全认同,很多时候非0退出状态码是无关紧要的,甚至有时候非0状态码才是继续执行的必要条件。
回到话题上。先看看"set -e"的效果。以下面的脚本为例,在脚本中,mv命令少给了一个参数,它是错误命令,返回的是非0状态码。
[root@linuxidc ~]# vim trap8.sh #!/bin/bash set -e echo "right here" mv ~/a.txt [ "$?" -eq 0 ] && echo "right again" || echo "wrong here"
如果不设置"set -e",那么会被下一条语句判断,但因为设置了"set -e",使得在mv错误发生时,就立即退出脚本所在的shell。也就是说,对"$?"的判断语句根本就是多余的。结果如下。
[root@linuxidc ~]# ./trap8.sh right here mv: missing destination file operand after ‘/root/a.txt’ Try 'mv --help' for more information.
可以设置ERR陷阱,专门捕获"set -e"起作用时的信号。例如,当命令错误时,做一些临时文件清理动作等。注意,当捕获到了ERR信号时,脚本不会再继续向下运行,而是trap处理结束后就立即退出。例如: