使用trap为shell的信号设置陷阱和陷阱运行原理以及如何复原默认信号处理

陷阱信号

当你的程序运行时,按下Control-C或者Control-, 一旦该信号到达程序就立刻终止运行。但是在很多的时候,你可能并不希望在信号到达的时候,程序就立刻停止运行。而是它能希望忽略这个信号而一直运行,或者在程序退出以前,做一些清除操作。trap命令允许你控制你的程序在收到信号以后的行为。
信号的定义是由一个进程发送给另一个进程的,或者在特定的键按下以后由操作系统发送给进程,又或者在异常情况下发生时,由数字组成的非同步的消息。trap命令告诉shell根据收到的信号以不同的方式终止当前的进程。如果trap命令后面跟着一个用引号引用的命令,则在接收到指定数字后,就执行这个命令。shell总共读取两次命令字符串,一次是在设置trap的时候,一次是在信号达到的时候。如果命令字符串被双引号引用,在第一次trap设置时就执行变量和命令替换。如果是用的单引号引用,那么等到信号到达trap开始执行的时候,才运行变量和命令替换。

格式
trap 'command; command' signal-num
trap 'command; command' signal-name

示例一

trap  'rm file; exit 1' 0 1 2 15
trap  'rm file; exit 1' EXIT HUP INT TERM
# 两种方式都一样

如果在一个脚本运行过程中,系统接到一个前缀SIG,例如SIGHUP,SIGINT等等。bash允许你在信号上使用象征性名称。例如没有前缀或者用数字作为信号的名称。一个叫做EXIT的或者数字0的伪信号将在shell退出时,导致一个陷阱的触发执行。
详细的信号说明见文档。常见的信号以及它们的数值代号、说明如下:
Signal Value Comment

SIGHUP 1 终止进程,特别是终端退出时,此终端内的进程都将被终止
SIGINT 2 中断进程,几乎等同于sigterm,会尽可能的释放执行clean-up,释放资源,保存状态等(CTRL+C)
SIGQUIT 3 从键盘发出杀死(终止)进程的信号

SIGKILL 9 强制杀死进程,该信号不可被捕捉和忽略,进程收到该信号后不会执行任何clean-up行为,所以资源不会释放,状态不会保存
SIGTERM 15 杀死(终止)进程,几乎等同于sigint信号,会尽可能的释放执行clean-up,释放资源,保存状态等

SIGSTOP 19 该信号是不可被捕捉和忽略的进程停止信息,收到信号后会进入stopped状态
SIGTSTP 20 该信号是可被忽略的进程停止信号(CTRL+Z)

信号复位:trap命令后面跟一个信号或者数字,可以把信号复位为默认动作。一旦调用了函数,函数设置的陷阱可以被调用这个函数的shell识别。同时,在函数外设置的陷阱也可以被函数识别。

示例一

trap 2 or trap INT #为信号2设置默认动作。当按下Ctrl-C就会触发这个信号
# 这样用法并不推荐,可能会产生副作用。
trap - signal-list # 这种方法和上面这种方法效果一样,更加稳妥。

忽略信号:如果trap命令后面跟一对空引号,列表中的信号就会被进程所忽略。

trap " " 1 2
trap " " HUP INT
#信号1和2将被shell进行忽略

**陷阱列表:**通过输入trap命令,可以显示陷阱的列表和分配给陷阱的命令清单。

trap的语法格式为:

1.trap [-lp]
2.trap cmd-body signal_list
3.trap ‘’ signal_list
4.trap signal_list
5.trap - signale_list

语法说明:
语法1:-l选项用于列出当前系统支持的信号列表,和"kill -l"一样的作用。
-p选项用于列出当前shell环境下已经布置好的陷阱。
语法2:当捕捉到给定的信号列表中的某个信号时,就执行此处给定cmd-body中的命令。
语法3:命令参数为空字符串,这时shell进程和shell进程内的子进程都会忽略信号列表中的信号。
语法4:省略命令参数,重置陷阱为启动shell时的陷阱。不建议此语法,当给定多个信号时结果会出人意料。
语法5:等价于语法4。
trap不接任何参数和选项时,默认为"-p"。

$ trap 'echo hello world' 2
$ trap # 显示默认设置的陷阱
# trap -- 'shell_session_update' EXIT
# trap -- 'echo hello world ' SIGKILL
# trap -- ' ' SIGINT

示例一

#!/usr/bin/bash
# scriptname: trapping.sh
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 "When you are ready to exit, please enter a \"stop.\""
while true
do
  echo "go ahead --->"
  read
  if [[ $REPLY == [sS]top ]] # read命令如果没有指定变量,则输入内容默认保存在REPLY中
  then
      break
  fi
done  
(The output)
$ bash trapping.sh 
#When you are ready to exit, please enter a "stop."
#go ahead --->
#w
#go ahead --->
#e
#go ahead --->
#^ZControl-Z will not terminate trapping.sh.
#^CControl-C will not terminate trapping.sh.
#^\Control-\ will not terminate trapping.sh.
#^CControl-C will not terminate trapping.sh.
#^CControl-C will not terminate trapping.sh.
#^CControl-C will not terminate trapping.sh.
#^CControl-C will not terminate trapping.sh.
#^CControl-C will not terminate trapping.sh.
#^CControl-C will not terminate trapping.sh.
#^CControl-C will not terminate trapping.sh.
#^CControl-C will not terminate trapping.sh.
#stop #前面的三个信号都触发了设置的命令,打印出了内容,但是并不会终止脚本运行,直到输入stop终止循环。

**复位信号:**trap命令后面以信号名字或者号码作为参数,可以复位信号为默认动作。当然也可以以trap - siglist的形式来复位信号默认行为。

trap 'trap - 2' 2
# 需要按两次才能终止进程

**函数中的陷阱:**如果使用陷阱处理函数中的信号,一旦函数被激活,它将影响整个脚本。陷阱对于脚本来说是全局的。在下面的例子中,陷阱被设置为忽略中断ctrl-c。要终止这个脚本的循环就只能使用kill命令。它证明了在函数中使用陷阱可能出现不可预测的情况。

#!/usr/bin/bash
function trapper {
   echo "In trapper"
   trap 'echo "Caught in a trap"' INT
}

while : #等同于true
do
  echo "In the main script"
  trapper
  echo "still in main"
  sleep 5
done  

(The output)
执行以上脚本以后的输出:

#In the main script
#In trapper
#still in main
#^CCaught in a trap
#In the main script
#In trapper
#still in main
#^CCaught in a trap
#In the main script
#In trapper
#still in main
上一篇:[Usaco2017 Jan]Balanced Photo


下一篇:nginx fpm 常见错误对比分析