项目中的升级脚本可能耗时很长,在这段时间内,脚本没有任何输出的,这带给市场部署人员的感觉就是脚本好像卡住了。通常情况下,部署人员都会直接CTRL+C停掉升级脚本,这会导致升级失败,最终需要开发人员介入去修复环境。
可以通过输出升级进度的方式提示部署人员升级正在进行中,但进度也可能在一段时间不动,而且无法避免意外终止升级的情况,此时可以使用Shell的内建命令trap来忽略SIGINT这些信号,保证升级不会中断。
trap介绍
trap的格式如下,功能就是设置信号处理行为
trap [-lp] [[arg] sigspec ...]
arg
可以是shell命令或者自定义函数sigspec
可以是以下的一个或多个-
- 定义在<signal.h>中的信号名或者数值。信号名的大小写不敏感,SIG这个前缀也是可选的。以下的命令的效果都是一样的
-
trap "echo 123" SIGINT
-
trap "echo 123" INT
-
trap "echo 123" 2
-
trap "echo 123" int
-
trap "echo 123" Int
-
- EXIT:在shell退出前执行trap设置的命令,也可以指定为0
-
- RETURN:在函数返回时,或者
.
和source
执行其他脚本返回时,执行trap设置的命令
- RETURN:在函数返回时,或者
-
- DEBUG:在任何命令执行前执行trap设置的命令,但对于函数仅在函数的第一条命令前执行一次
-
-
foo()
-
{
-
echo "foo 1"
-
echo "foo 2"
-
}
-
-
trap "echo 123" DEBUG
-
foo
执行结果
-
123 # 在函数前执行一次
-
foo 1
-
foo 2
- ERR:在命令结果为非0时,执行trap设置的命令
常用方式
trap -l
:列出所有信号的数值和名字,类似于kill -l
-
[root@localhost ~]# 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
-
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
-
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
-
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
-
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
-
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
-
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
-
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
-
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
-
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
-
63) SIGRTMAX-1 64) SIGRTMAX
trap -p
:列出通过trap设置的信号处理命令。
-
[root@localhost ~]# trap "echo INT" INT
-
[root@localhost ~]# trap -p INT
-
trap -- 'echo INT' SIGINT
trap "" sigspec
:忽略sigspec指定的信号trap "do something" sigspec
:收到sigspec指定的信号时,do some thing后,继续执行后续命令。trap sigspec
ortrap - sigspec
:恢复sigspec指定的信号的默认行为
trap的注意事项
- trap可以在收到信号前的任意位置设置,并非需要在脚本的第一行,但是shell是按照顺序执行语句的,不会优先执行trap
-
-
trap -p INT # 不输出任何信息
-
trap "echo get signal" INT
- 在函数中设置trap,也是全局生效的
-
-
foo()
-
{
-
trap "echo get signal" INT
-
}
-
foo
-
trap -p INT # 输出trap -- 'echo get signal' SIGINT
- 对于同一个信号,只有最后一次trap生效
- trap只在本进程内有效,它的子进程不会继承trap的设置。
main.sh
-
-
trap "get signal" INT
-
./sub.sh
sub.sh
-
-
trap -p INT # 没有任何信息
- 如果子进程阻塞着,当通过kill直接杀死父进程时,只有等到子进程退出,父进程才会处理信号。kill -2 杀掉以下脚本的进程,此时需要等待10秒后,才会输出"get signal"。因为CTRL+C的信号是发送给进程组,此时sleep进程被INT信号中断了,所以立即输出了"get signal",可以用Kill -2 发送信号到进程组达到一样的效果。
-
-
trap "get signal" INT
-
sleep 10
还有一个变通的方法就是把sleep放在后台进行,并用wait等待,wait是shell的内建命令,会被本进程收到的信号直接打断,此时sleep是继续在后台执行的。
-
-
trap "get signal" INT
-
sleep 10 & wait $!
- 处理SIGINT或者SIGQUIT时,需要特别注意。比如下面的脚本,CTRL+C后只是中断了一次sleep,当信号处理结束后,又会进入下一次sleep,这可能并不符合预期。
-
-
trap "echo get signal" INT
-
sleep 10
-
sleep 10
需要在处理信号中,将信号处理恢复到默认,并以INT信号再次杀掉自己
-
-
trap "echo get signal;trap - INT;kill -s INT "$$" " INT
-
sleep 10
-
sleep 10