Skip to content

Latest commit

 

History

History
73 lines (61 loc) · 2.84 KB

记录一个trap和wait的小问题.md

File metadata and controls

73 lines (61 loc) · 2.84 KB

记录一下工作中遇到的一个坑点

事情是这样的,因为我修改了线上所有的bash,就导致各种shell脚本遇到了问题都会找到我这儿,然后今天就遇到一个情况,我的同事写了大概这样一个脚本

#!/bin/bash
pid=
function handle_exit {
 sleep 10
 if [ x"${pid}" != x ]
 then
  echo "kill -15 ${pid}"
  kill -15 ${pid}
 fi
}
trap handle_exit INT TERM QUIT
./server.sh &
pid=$!
wait ${pid} 
exit_code=$?
echo "exit with code ${exit_code}"
exit ${exit_code}

然后他的需求是获得server.sh退出时的返回码。 代码看起来合情合理,然而问题就来了,在实际使用的时候,子进程还没有退出呢,父进程就直接退出了,我测了几下,直觉上是wait ${pid}根本没有生效,但是又没有弄懂为什么。

首先是关于trap,这个指令的执行很有意思,我本以为只要脚本接收到信号,就会里面转入到trap的逻辑之中,事实上大多数文章也是这么教导的,无论当前的执行状态是什么,只要一个ctrl+c就可以看到trap中的逻辑执行,这不明显是信号来了就执行吗?

然而事实却并非如此,写一个这样的脚本:

#!/bin/bash
function _exit {
 echo "exit with trap"
}
trap _exit INT TERM QUIT
sleep 25

然后通过ps xf -o pid,ppid,args找到这个脚本的PID,接着通过kill -15 PID来结束这个进程,可以清晰的发现一直等sleep 25执行完了才会进入到_exit的逻辑中,而ctrl+c出现的现象其实是因为ctrl+c本身是向整个进程组发送SIGINT信号,而sleep是属于当前进程组的,因此也会接收到此信号从而直接退出。

说白了,结论就是trap会等待当前命令结束以后再去处理结束队列中的信号。

那上面的问题就很有意思了,进程当前状态是一个wait状态,因此直接发送kill -15的话会导致这个wait直接退出,然后才是去执行trap中的逻辑退出子进程,再加上子进程后台进程,自然就出现了父进程先退出,且无法获取返回码的情况。

那么实际上只要让trap之后再执行一次wait就好了,第一个wait会被退出,那第二个wait就会在trap后执行,因此应该这么改一下脚本:

#!/bin/bash
pid=
function handle_exit {
 sleep 10
 if [ x"${pid}" != x ]
 then
  echo "kill -15 ${pid}"
  kill -15 ${pid}
 fi
}
trap handle_exit INT TERM QUIT
./server.sh &
pid=$!
wait ${pid} 
trap - INT TERM QUIT
wait ${pid}
exit_code=$?
echo "exit with code ${exit_code}"
exit ${exit_code}

参考资料