返回首页 两个月精通 Shell 脚本

shell 学习五十三天----捕获信号 trap

捕捉进程信号

信号是一种进程间的通信机制,它给应用程序提供一种异步的软件中断,是应用程序有机会接受其他程序活终端发送的命令 (即信号)。应用程序收到信号后,有三种处理方式:忽略,默认,捕捉。该进程收到一个信号后,会检查对该信号的处理机制。如果是 SIG_IGN,就会忽略该信号;如果是 SIG_DFT,则会采用系统默认的处理动作,通常是终止进程或忽略该信号;如果给该信号指定了一个处理函数 (捕捉),则会中断当前进程正在执行的任务,转而去执行该信号的处理函数,返回后在继续执行被中断的任务。

在有些情况下,我们不希望自己的 shell 脚本在运行时刻被中断,比如说我们写的 shell 脚本设为某一用户的默认 shell,使这一用户进入系统后只能做某一项工作,如数据库备份,我们可不希望用户使用 Ctrl+C 键便能进入到 shell 状态,做我们不希望看到的事情,这便用到了信号处理。

以下是一些常见的信号:

```信号名称 信号数 说明 SIGHUP 1 本信号在用户终端连接 (正常或非正常) 结束时发出,通常是在终端的控制进程结束时,通知同一 session 内的各个作业,这时它们与控制终端不再关联。登录 Linux 时,系统会分配给登录用户一个终端 (Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个 Session。当用户退出 Linux 登录时,前台进程组和后台有对终端输出的进程将会收到 SIGHUP 信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。 SIGINT 2 程序终止 (interrupt) 信号,在用户键入 INTR 字符 (通常是 Ctrl+C) 时发出 SIGQUIT 3 和 SIGINT 类似,但由 QUIT 字符 (通常是 Ctrl /) 来控制。进程在因收到 SIGQUIT 退出时会产生 core 文件,在这个意义上类似于一个程序错误信号。 SIGFPE 8 在发生致命的算术运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为 0 等其它所有的算术的错误。 SIGKILL 9 用来立即结束程序的运行。本信号不能被阻塞,处理和忽略。 SIGALRM 14 时钟定时信号,计算的是实际的时间或时钟时间。alarm 函数使用该信号 SIGTERM 15 程序结束 (terminate) 信号,与 SIGKILL 不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。shell 命令 kill 缺省产生这个信号。



**捕获信号**

当按下了 `Ctrl+C` 键或 `break` 键在终端一个 shell 程序的执行过程中,正常程序将立即终止,并返回命令提示符。这可能并使总是可取的,例如,你可能最终留下了一堆临时文件,将不会清理。

捕获这些信号是很容易的,`trap` 命令的语法如下:

`\#trap commands signals`

这里的 `commands` 可以是任何有效的 linux 命令,或一个用户定义的函数,信号可以是任意数量的信号,你想来捕获的列表。

- 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`

注意:

1. 对信号 11(段违例) 不能捕捉,因为 shell 本身需要捕捉该信号去进行内存的转储。
2. 在 trap 中可以定义对信号 0 的处理 (实际上没有这个信号),shell 程序在其终止 (如执行 exit 语句) 时发出该信号。
3. 在捕捉到 `signal-list` 中指定的信号并执行完相应的命令之后,如果这些命令没有将 shell 程序终止的话,shell 程序将继续执行收到信号时所执行的命令后面的命令,这样将很容易导致 shell 程序无法终止。另外,在 `trap` 语句中,单引号和双引号是不同的,当 shell 程序第一次碰到 `trap` 语句时,将把 `commands` 中的命令扫描一遍。此时若 `commands` 是用单引号括起来的话,那么 shell 不会对 `commands` 中的变量和命令进行替换,否则 `commands` 中的变量和命令将用当时具体的值来替换。

**trap 命令用于指定在接收到信号后将要采取的动作。常见的用途是在脚本程序被中断时完成清理工作。**

测试案例:

按照用户的要求,我们需要屏蔽的是 `HUP INT QUIT TSTP` 几个信号。所以,可以运行:
`\#trap “”HUP INT QUIT TSTP`

这个时候,可以试试打开一个持续的命令,然后中断其运行,例如:
`\#tail -f /var/log/messages`

接着,试试用 `Ctrl+C` 或 `Ctrl+\` 来中断试试,该进程是不会退出的。

**恢复信号** 

如果想恢复的话,可以用 `Ctrl+Z` 吧进程放到后台,然后运行:
`\#trap :HUP INT QUIT TSTP`
然后,用 `#ps -ef` 看看其 PID 号,`bg 1` 让程序继续运行,最后用 kill 杀掉即可。

**其他** 

可以试试运行:
`\#trap “echo ‘hello world’” HUP INT QUIT TSTP`
这样,当你运行 `Ctrl+C` 等中断时,会自动运行 `echo` 命令,结果就是实现 helloworld 字符串。

**引用**  

`\#tail -f /var/log/messages`
注意,这方式并不能屏蔽中断,按下 `Ctrl+C` 键仍然会退出程序,仅会再运行一个额外的命令而已。