记录一下工作中遇到的一个坑点
事情是这样的,因为我修改了线上所有的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}