shell中trap的使用

起因

项目中的升级脚本可能耗时很长,在这段时间内,脚本没有任何输出的,这带给市场部署人员的感觉就是脚本好像卡住了。通常情况下,部署人员都会直接CTRL+C停掉升级脚本,这会导致升级失败,最终需要开发人员介入去修复环境。

可以通过输出升级进度的方式提示部署人员升级正在进行中,但进度也可能在一段时间不动,而且无法避免意外终止升级的情况,此时可以使用Shell的内建命令trap来忽略SIGINT这些信号,保证升级不会中断。

trap介绍

trap的格式如下,功能就是设置信号处理行为

trap [-lp] [[arg] sigspec ...]
  • arg可以是shell命令或者自定义函数
  • sigspec可以是以下的一个或多个
    • 定义在中的信号名或者数值。信号名的大小写不敏感,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

参考资料

  • SignalTrap



作者:南边风景好
链接:https://www.jianshu.com/p/07220a5d855a
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

你可能感兴趣的:(Shell)