bash 下的信号陷阱
从异常返回
就像在其他编程语言中使用信号一样,信号捕获很容易以错误的方式完成;
使用时,trap
您不必在陷阱评估中处理您的函数,而只需设置一个标志,主程序可以在陷阱异常结束后检查,以尽可能缩短异常执行时间。
特别是,您不必在陷阱执行级别启动fork
子进程!
一个正确的例子
#!/bin/bash
SERVER_PID=""
CMD_TRAP=""
npm() { #Doing something that could be checked from external
if [ "$1" ] && [ "$1" == "start" ] ;then
while :;do
date "+%s%N" >/tmp/dummyfile.txt
sleep .333
done
fi
}
start_server() {
npm start &
SERVER_PID=$!
}
terminate_server() {
[ "$SERVER_PID" ] && ps $SERVER_PID &>/dev/null && kill -TERM $SERVER_PID
SERVER_PID=""
}
refresh_server() {
terminate_server
start_server
}
printf "for:\n server restart, hit: 'kill -USR2 %d'\n" $$
printf " server stop, hit: 'kill %d' (or Ctrl+C)\n" $$
trap 'CMD_TRAP=refresh' USR2 HUP
trap 'CMD_TRAP=terminate' TERM INT
start_server
while [ "$SERVER_PID" ];do
wait $SERVER_PID
case "$CMD_TRAP" in
refresh ) refresh_server ;;
terminate ) terminate_server ;;
* ) refresh_server ;; # in case server just end.
esac;
CMD_TRAP=""
[ "$SERVER_PID" ] && echo "LOOP." || echo "EXIT."
done
特征
这个演示脚本做:
- 服务将在脚本启动时启动,
USR2
如果收到一个或一个HUP
信号,服务将重新启动,
- 如果它刚刚完成或收到任何信号,服务将重新启动并且
- 服务将正确停止
TERM
是收到信号或
- 异常处理正确(立即返回主程序)
- 没有不需要的/非托管的错误消息
输出样本
窗口1
tty
/dev/pts/0
窗口2
ps --tty pts/0 fw
PID TTY STAT TIME COMMAND
2996 pts/0 Ss 0:01 bash
5187 pts/0 S+ 0:00 \_ bash
窗口1
./serverScript.sh
for:
server restart, hit: 'kill -USR2 11469'
server stop, hit: 'kill 11469' (or Ctrl+C)
窗口2
ps --tty pts/0 fw
PID TTY STAT TIME COMMAND
2996 pts/0 Ss 0:01 bash
5187 pts/0 S 0:00 \_ bash
11469 pts/0 S+ 0:00 \_ /bin/bash ./servermon.sh
11470 pts/0 S+ 0:00 \_ /bin/bash ./servermon.sh
cat /tmp/dummyfile.txt
1361603642256133674
cat /tmp/dummyfile.txt
1361603648712606114
ps --tty pts/0 fw
PID TTY STAT TIME COMMAND
2996 pts/0 Ss 0:01 bash
5187 pts/0 S 0:00 \_ bash
11469 pts/0 S+ 0:00 \_ /bin/bash ./servermon.sh
11470 pts/0 S+ 0:01 \_ /bin/bash ./servermon.sh
16814 pts/0 S+ 0:00 \_ sleep .333
kill -USR2 11469
窗口1
LOOP.
窗口2
ps --tty pts/0 fw
PID TTY STAT TIME COMMAND
2996 pts/0 Ss 0:01 bash
5187 pts/0 S 0:00 \_ bash
11469 pts/0 S+ 0:00 \_ /bin/bash ./servermon.sh
17152 pts/0 S+ 0:00 \_ /bin/bash ./servermon.sh
17532 pts/0 S+ 0:00 \_ sleep .333
cat /tmp/dummyfile.txt
1361604208069564188
cat /tmp/dummyfile.txt
1361604209103660589
kill -USR2 11469
窗口1
LOOP.
窗口2
cat /tmp/dummyfile.txt
1361604278583723517
cat /tmp/dummyfile.txt
1361604279605292149
kill 11469
窗口1
EXIT.
$
窗口 1 终止循环和服务器子进程。
窗口1
./serverScript.sh
for:
server restart, hit: 'kill -USR2 19232'
server stop, hit: 'kill 19232' (or Ctrl+C)
然后如果Ctrl+C被按下:
窗口1
^CEXIT.
$
窗口2
ps --tty pts/0 fw
PID TTY STAT TIME COMMAND
2996 pts/0 Ss 0:01 bash
5187 pts/0 S+ 0:00 \_ bash
解释
无法堆叠异常。因此,在执行异常时,可以忽略另一个中断:
如果对于示例,这可能变得很重要,您的refresh_server()
函数必须在重新启动服务器之前压缩和轮换一些日志:
refresh_server() {
terminate_server
lockedfilename=-$(date +%F_%H-%M-%S-$$)
mv /srv/logfile /srv/logfile-$lockedfilename
gzip /srv/logfile-$lockedfilename
start_server
}
许多中断可以在主循环中汇总或忽略,但处理必须仅在主级别完成。
有一个关于问题所在的小演示:
窗口1
trap "echo USR2 sleep 4;sleep 4" USR2
while :;do printf "\r%s " $(date +%s%N);sleep .333;done
1361606565xxxxxxxxx
(xxxxxxxxx
改变 3x / 秒)
窗口2
for ((i=5;i--;));do echo KILL;kill -USR2 5187;sleep .5;done
KILL
KILL
KILL
KILL
KILL
1361606722582175969 USR2 sleep 4
USR2 sleep 4
1361606770xxxxxxxxx
我的循环中只有两个超过五次的中断被困住。
如果您搜索解释:5 x 0,5 = 2,5 秒。它远少于 4 秒睡眠,那么为什么我收到第二个中断,但不是全部五个?
在主循环中解开陷阱
在主循环中解开陷阱有一个小技巧:
while [ "$SERVER_PID" ];do
wait $SERVER_PID
OLDIFS="$IFS" IFS=$'\n'
TRAPS=($(trap)) # save traps
IFS="$OLDIFS"
eval "$(printf "trap -- %s\n" ${TRAPS[@]##*\'})" # untrap
case "$CMD_TRAP" in
refresh ) refresh_server ;;
terminate ) terminate_server ;;
* ) refresh_server ;; # in case server just end.
esac;
CMD_TRAP=""
if [ "$SERVER_PID" ]
then echo "LOOP."
else echo "EXIT."
eval "$(printf "%s\n" "${TRAPS[@]}")" # restore traps
fi
done