项目中的升级脚本可能耗时很长,在这段时间内,脚本没有任何输出的,这带给市场部署人员的感觉就是脚本好像卡住了。通常情况下,部署人员都会直接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设置的命令 - DEBUG:在任何命令执行前执行trap设置的命令,但对于函数仅在函数的第一条命令前执行一次
-
#!/bin/bash -
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
or trap - sigspec
:恢复sigspec指定的信号的默认行为
trap的注意事项
- trap可以在收到信号前的任意位置设置,并非需要在脚本的第一行,但是shell是按照顺序执行语句的,不会优先执行trap
-
#!/bin/bash -
trap -p INT # 不输出任何信息 -
trap "echo get signal" INT
- 在函数中设置trap,也是全局生效的
-
#!/bin/bash -
foo() -
{ -
trap "echo get signal" INT -
} -
foo -
trap -p INT # 输出trap -- 'echo get signal' SIGINT
- 对于同一个信号,只有最后一次trap生效
- trap只在本进程内有效,它的子进程不会继承trap的设置。
main.sh
-
#!/bin/bash -
trap "get signal" INT -
./sub.sh
sub.sh
-
#!/bin/bash -
trap -p INT # 没有任何信息
- 如果子进程阻塞着,当通过kill直接杀死父进程时,只有等到子进程退出,父进程才会处理信号。kill -2 杀掉以下脚本的进程,此时需要等待10秒后,才会输出"get signal"。因为CTRL+C的信号是发送给进程组,此时sleep进程被INT信号中断了,所以立即输出了"get signal",可以用Kill -2 发送信号到进程组达到一样的效果。
-
#!/bin/bash -
trap "get signal" INT -
sleep 10
还有一个变通的方法就是把sleep放在后台进行,并用wait等待,wait是shell的内建命令,会被本进程收到的信号直接打断,此时sleep是继续在后台执行的。
-
#!/bin/bash -
trap "get signal" INT -
sleep 10 & wait $!
- 处理SIGINT或者SIGQUIT时,需要特别注意。比如下面的脚本,CTRL+C后只是中断了一次sleep,当信号处理结束后,又会进入下一次sleep,这可能并不符合预期。
-
#!/bin/bash -
trap "echo get signal" INT -
sleep 10 -
sleep 10
需要在处理信号中,将信号处理恢复到默认,并以INT信号再次杀掉自己
-
#!/bin/bash -
trap "echo get signal;trap - INT;kill -s INT "$$" " INT -
sleep 10 -
sleep 10