名称 值 描述 SIGHUP 1 控制终端发现被挂起或控制进程死亡 SIGINT 2 键盘终端 SIGQUIT 3 来自键盘的退出信号 SIGKILL 9 杀死进程的信号 SIGALRM 14 定时时钟中断 SIGTERM 15 终止信号
trap 'command_list' signals
trap 'echo you hit Ctrl-C\, now exiting..; exit' SIGINT
捕获 SIGINT, 什么也不作,即忽略该信号,使用html
trap '' SIGINT
重置 SIGINT,即便用系统默认的信号处理函数shell
trap - SIGINT
本例中,能够捕获 SIGTERM 信号,并指定信号处理函数来实现资源的清理工做,资源清理干净后再退出。编程
horen@heart$~> cat signal.sh #!/bin/bash trap 'TaskClean; exit' SIGTERM function TaskOne() { echo "Now do task one..." sleep 10 echo "TaskOne is done" } function TaskTwo() { echo "Now do task two..." sleep 10 echo "TaskTwo is done" } function TaskClean() { echo "Now do task clean..." sleep 2 echo "TaskClean is done" } TaskOne TaskTwo
运行
bash
horen@heart$~> sh signal.sh Now do task one... Now do task clean... TaskClean is done
在执行 TaskOne 时给脚本发送信号 SIGTERM,脚本当时在执行 “sleep”,sleep 结束时转而去执行信号处理函数。注意,并非在 TaskOne 结束后再去执行信号处理函数。 ide
trap的一些使用说明:函数
它有三种形式分别对应三种不一样的信号回应方式.
第一种:
trap 'commands' signal-list
当脚本收到 signal-list 清单内列出的信号时, trap 命令执行双引号中的命令.
第二种:
trap signal-list
trap 不指定任何命令, 接受信号的默认操做. 默认操做是结束进程的运行.
第三种:
trap ' ' signal-list
trap 命令指定一个空命令串, 容许忽视信号.工具
NOTE:trap 对同种 signal 只能相应一种设定,若是在一个 shell 里面设置多个 trap,如:ui
trap 'echo “aaaaaaaaaaa”' INT trap 'echo “bbbbbbbbbbb”' INT
那么它只会响应最后一个信号设定。spa
信号处理(Signal Handling)在 Linux 编程中一直扮演者重要的角色,几乎每一个系统工具都要用到它,最多见的功能莫过于用信号进行进程间通讯(尤为是父子进程)以及捕捉 SIGINT、SIGTERM 之类的退出信号以作一些善后处理(cleanup)。C 中自没必要多说,可使用 wait 族函数;而 shell 脚本中也有捕捉信号的 trap 功能——然而许多人在使用 trap 功能的时候却存在着这样那样的误解,这些看似可有可无的小细节最后有可能使得你的脚本与你预想的行为彻底不一样。.net
如无特殊说明,下文所指 shell 均以 Bash 为例。
虽然我很想说这些应当要本身看 manpage ,但考虑到也许正在读文章的你手边没有 Linux ,仍是简单说一下吧。
1
|
USAGE:
trap [action condition ...]
|
即当捕捉到 condition 列表所对应的任何一个信号时,执行 action 动做(使用 eval action
来执行,故 action 能够是 shell 内建指令、外部命令及脚本中的函数等)。action 还但是”"(空)、’-'等,分别表明忽略相应信号及重置相应信号为默认行为。
condition 中的信号到底应该如何书写?好比终端中断信号(通常用 CTRL-C 发出),究竟是写 SIGINT 、 INT 仍是 2(大部分系统上该信号对应的信号数)?是大写仍是小写?
若是你使用最新版的 Bash ,那么这几种写法均可以。而若是你须要在不一样 shell 中保持可移植性,请使用大写、不带前缀的 INT !根据 POSIX 标准, trap 的 condition 不该当加上 SIG 前缀,且必须全大写,容许带 SIG 前缀或小写是某些 shell 的扩展功能。而信号数在不一样的系统上可能不一样,因此也不是一个好主意。
许多资料,尤为是中文资料中不容申辩地指明—— trap 必须放在脚本中第一个非注释行。事实果然如此么?
不管是 manpage 仍是 POSIX 文档中,我都没有找到任何与之相关的说明。甚至在 TLDP 的 Bash Guide for Beginners 中,多个例子都分明把 trap 放在了脚本的中间。最后我在这篇文档中找到了下面这句常常被误读的话:
Normally, all traps are set before other executable code in the shell script is encountered, i.e., at the beginning of the shell script.
果真,这只是一个为了保证信号钩子尽早被设立的一个设计惯例罢了。事实上, trap 能够根据你的须要放在脚本中的任何位置。脚本中也能够有多个 trap ,能够为不一样的信号定义不一样的行为,或是修改、删除已定义的 trap 。更进一步地, trap 也有做用范围,你能够把它放在函数中,它将只在这个函数里起效!你看,其实 trap 的行为是很符合 UNIX 的惯例的。
这是本文最重要的一点。信号究竟是何时被处理的?更准确地说,好比脚本正在执行某个命令时收到了某个信号,那么它会被当即处理,仍是要等待当前命令完成?
我不打算直接说明答案。为了让咱们对这个问题有更透彻的理解,让咱们来作一下实验。看下面这个时常被用来说解 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 是内建命令,会被打断。)
那么上文的例子应当要如何写才能达到想要的效果呢?有两种方法:1、把 sleep 放到后台进行,再用内建的 wait 去等待其执行结束(详见上一段提到的那篇文档);2、暴力一点,把一长段 sleep 拆成一秒的小 sleep 的循环,这在对精度要求不高的状况下也是一个可行的办法。