shell中的信号处理

在unix里,可能发生的每一种类型的事件都是由一个独立的信号来描述,每一个信号都是一个小的正整数,如:
名称    值     描述
SIGHUP  1      控制终端发现被挂起或控制进程死亡
SIGINT  2      键盘终端
SIGQUIT 3      来自键盘的退出信号
SIGKILL 9      杀死进程的信号
SIGALRM 14     定时时钟中断
SIGTERM 15     终止信号

使用:kill -l 列出所支持的信号

发送信号:kill -signal pid
SIGTERM: kill pid等价于kill -s SIGTERM pid
如: kill -s SIGHUP 1001; 等价于 kill -1 1001,一个使用信号名称,一个使用代表该信号的整数

SIGKILL: SIGKILL有不能被捕捉的特殊能力,任何接受到它的进程都要立即终止,kill -9 1001

例子:

<span style="font-size:14px;">#keepalive.sh
#!/bin/bash

trap "rm -f ${TMPF}; exit 2" 1 2 3 15 #当脚本收到SIGHUP, SIGINT, SIGQUIT,SIGTERM时,临时文件会被删除

TMPF=".arch"
uname -m > "${TMPF}"
read ARCH < "${TMPF}"
rm -f "${TMPF}"
echo ${ARCH}
exit 0
--------------------------------------------------------------

#!/bin/bash

PROG="$1"

if [ "$PROG" = "" ]; then
	echo "Usage: $0 cmd. "
	exit 1
fi

Init()
{
	if [ "$!" != "" -a "$!" != "0" ]; then
		if kill -0 "$!" > /dev/null 2>&1; then
			kill "$!" > /dev/null 2>&1 || return
		fi
	fi
	$PROG &
}

CleanUp()
{
	if [ "$!" != "" -a "$!" != "0" ]; then
		kill -9 "$!" > /dev/null 2>&1
	fi
	exit 2
}

trap CleanUp 2 3 15
trap Init 1

while :
do
	if [ "$!" != "" -a "$!" != "0" ]; then
		wait "$!"
	fi
	
	$PROG &
done

exit 0
上面这个脚本启动由第一个参数指定的程序,并把它放到后台执行,然后等待这个程序结束
当这个程序结束时,脚本再次启动它,当接收到信号SIGINT,SIGQUIT,SIGTERM时,脚本
才退出,如果脚本接收到信号SIGUP,它将重新启动这个程序。
如:bash keepalive.sh ls
stty -a可以列出信号与键盘按键的对应关系,SIGINT为ctrl+c,SIGQUIT为ctrl+\

---------------------------------------------------------------------------------
shell定时器安装:
#!/bin/bash

PROG=$1

AlarmHandler()
{
	echo "Got SIGALAM, cmd took too long"
	KillSubProcs
	exit 14
}

KillSubProcs()
{
	kill ${CHPROCIDS:-$!}
	if [ $? -eq 0 ];then
		echo "Sub-processes killed."
	fi
}
 
SetTimer()
{
	DEF_TOUT=${1:-10};
	if [ $DEF_TOUT -ne 0 ];then
		sleep $DEF_TOUT && kill -s 14 $$ &
		CHPROCIDS="$CHPROCIDS $!"
		TIMERPROC=$!
	fi
}

UnsetTimer()
{
	echo "Start to unset timer"
	kill $TIMERPROC
}
 
trap AlarmHandler 14
find / -name "1.txt" & #使用一个耗时间长的命令
SetTimer 15  #设置15秒
CHPROCIDS="$CHPROCIDS $!"
wait $!
sleep 40
UnsetTimer
echo "ALL Done."
exit 0

-------------------------------------------------------------</span>

一. trap捕捉到信号之后,可以有三种反应方式:

  (1)执行一段程序来处理这一信号

  (2)接受信号的默认操作

  (3)忽视这一信号

  二. trap对上面三种方式提供了三种基本形式:

  第一种形式的trap命令在shell接收到signal list清单中数值相同的信号时,将执行双

  引号中的命令串。

  trap 'commands' signal-list

  trap "commands" signal-list

  为了恢复信号的默认操作,使用第二种形式的trap命令:

  trap signal-list

  第三种形式的trap命令允许忽视信号

  trap " " signal-list
  trap '' siganl-list
  trap : signal-list
    
在关键操作中忽略信号:
trap '' 1 2 3 15
DoImportantStuff
trap 1 2 3 15

注意:

  (1) 对信号11(段违例)不能捕捉,因为shell本身需要捕捉该信号去进行内存的转储。

  (2) 在trap中可以定义对信号0的处理(实际上没有这个信号), shell程序在其终止(如

  执行exit语句)时发出该信号。

  (3) 在捕捉到signal-list中指定的信号并执行完相应的命令之后, 如果这些命令没有

  将shell程序终止的话,shell程序将继续执行收到信号时所执行的命令后面的命令,这样将

  很容易导致shell程序无法终止。

  另外,在trap语句中,单引号和双引号是不同的,当shell程序第一次碰到trap语句时,

  将把commands中的命令扫描一遍。此时若commands是用单引号括起来的话,那么shell不会

  对commands中的变量和命令进行替换, 否则commands中的变量和命令将用当时具体的值来


kill -l可以列出系统的信号

通常我们需要忽略的信号有四个,即:HUP, INT, QUIT, TSTP,也就是信号1, 2, 3, 24
使用这样的语句可以使这些中断信号被忽略:
trap "" 1 2 3 24 或 trap "" HUP INT QUIT TSTP
用 trap :1 2 3 24 或 trap HUP INT QUIT TSTP使其回复默认值。
用stty -a可以列出中断信号与键盘的对应,分别执行上面的命令后,运行
tail -f /etc/passwd, 然后尝试用键盘中断,试试两种情况(默认和忽略)下有何不同。
更方便的是我们可以用在shell中用trap定义我们自己的信号处理程序

<span style="font-size:14px;">#!/bin/bash
#scriptname: trapping
#can use the singnal numbers of bash abbreviations seen
#below. Cannot use SIGINT ,SIGOUIT ,etc
trap 'echo Control-c will not terminate $0. ' INT
trap 'echo Control-\ will not terminate $0. ' QUIT
trap 'echo Control-Z will not terminate $0. ' TSTP
echo "Enter any string after the prompt. When you are ready to exit ,type \"stop\"."


while :
do
	echo -n "Go ahead ...>"
	read reply
	if [[ $reply == [sS]top ]]; then
   		break
	fi
done</span>

ref: http://www.cppblog.com/prayer/archive/2010/03/28/110743.html


NOTE:trap 对同种signal只能相应一种设定,如果在一个shell里面设置多个trap,如:

trap   ' echo “aaaaaaaaaaa”  '  INT

trap   ' echo “bbbbbbbbbbb”  '  INT

那么它只会响应最后一个信号设定。

3. 信号究竟是在什么时候被 trap 处理?

这是本文最重要的一点。信号到底是什么时候被处理的?更准确地说,比如脚本正在执行某个命令时收到了某个信号,那么它会被立即处理,还是要等待当前命令完成?

我不打算直接说明答案。为了让我们对这个问题有更透彻的理解,让我们来做一下实验。看下面这个时常被用来讲解 trap 的脚本:

1
2
3
#!/bin/bash
trap 'echo "INTERRUPTED!"; exit' INT
sleep 100

大多数教程都是这么做的,运行这个脚本,按下 CTRL-C 。你看到了什么?脚本打出了“INTERRUPTED!”并停止了运行。这看起来似乎很正常、很直觉——以此看来, trap 会立即捕捉到信号并执行,不管当前正在执行的命令。许多脚本也正是在这个假设下写的。

然而真的是这样么?让我们做另一个实验——在一个终端执行这个脚本,并打开另一个终端,用ps-ef|grepbash找到这个脚本的进程号,然后用kill-SIGINT pid向这个进程发送 SIGINT 信号。你在原先的终端中看到了什么?没有任何反应!如果你愿意等上100秒,你最终会看到“INTERRUPTED!”被输出。这样看来 trap 是等到当前命令结束以后再处理信号。

这样的矛盾究竟是为什么?问题其实出在 CTRL-C 身上。 Bash 等终端的默认行为是这样的:当按下 CTRL-C 之后,它会向当前的整个进程组发出 SIGINT 信号。而 sleep 是由当前脚本调用的,是这个脚本的子进程,默认是在同一个进程组的,所以也会收到 SIGINT 并停止执行,返回主进程以后 trap 捕捉到了信号。

这篇文档给了我们一个更准确的说明——如果当前正有一个外部命令在前台执行,那么 trap 会等待当前命令结束以后再处理信号队列中的信号。(而许多教程出错的另一个原因就是——某些 shell 中 sleep 是内建命令,会被打断。)

那么上文的例子应当要如何写才能达到想要的效果呢?有两种方法:一、把 sleep 放到后台进行,再用内建的 wait 去等待其执行结束(详见上一段提到的那篇文档);二、暴力一点,把一长段 sleep 拆成一秒的小 sleep 的循环,这在对精度要求不高的情况下也是一个可行的办法(这应该不用写范例了吧?)。

ref: http://blog.youkuaiyun.com/elbort/article/details/8525599

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值