本文目录:

1.1 信号说明

1.2 trap布置陷阱

1.3 布置完美陷阱必备知识

家里有老鼠,快消灭它!哎,又给跑了。老鼠这小东西跑那么快,想直接直接消灭它还真不那么容易。于是,老鼠药、老鼠夹子或老鼠笼就派上用场了,它们都是陷阱,放在那静静地等待着老鼠的光顾。

在shell中,也可以捉"老鼠",捉到"老鼠"后,可以无视它、杀死它或者抓起来逗一番。只需使用内置命令trap(中文就翻译为陷阱、圈套)就可以布置一个陷阱,这个陷阱当然不是捕老鼠的,而是捕捉信号。

通常trap都在脚本中使用,主要有2种功能:

(1).忽略信号。当运行中的脚本进程接收到某信号时(例如误按了CTRL+C),可以将其忽略,免得脚本执行到一半就被终止。

(2).捕捉到信号后做相应处理。主要是清理一些脚本创建的临时文件,然后退出。

1.1 信号说明

详细的信号说明见:信号。常见的信号以及它们的数值代号、说明如下:

Signal Value Comment

─────────────────────────────

SIGHUP1终止进程,特别是终端退出时,此终端内的进程都将被终止

SIGINT2 中断进程,几乎等同于sigterm,会尽可能的释放执行clean-up,释放资源,保存状态等(CTRL+C)

SIGQUIT3从键盘发出杀死(终止)进程的信号

SIGKILL9 强制杀死进程,该信号不可被捕捉和忽略,进程收到该信号后不会执行任何clean-up行为,所以资源不会释放,状态不会保存

SIGTERM15 杀死(终止)进程,几乎等同于sigint信号,会尽可能的释放执行clean-up,释放资源,保存状态等

SIGSTOP19该信号是不可被捕捉和忽略的进程停止信息,收到信号后会进入stopped状态

SIGTSTP20 该信号是可被忽略的进程停止信号(CTRL+Z)

每个信号其真实名称并非是SIGXXX,而是去除SIG后的单词,每个信号还有其对应的数值代号,在使用信号时,可以使用这3种方式中的任一一种。例如SIGHUP,它的信号名称为HUP,数值代号为1,发送HUP信号时,以下3种方式均可。

kill -1PIDkill -HUP PIDkill -SIGHUP PID

在上面所列的信号列表中,KILL和STOP这两个信号无法被捕捉。一般来说,在设置信号陷阱时,只会考虑HUP、INT、QUIT、TERM这4个会终止、中断进程的信号。

1.2 trap布置陷阱

trap的语法格式为:

1. trap [-lp]2. trap cmd-body signal_list3. trap ''signal_list4. trap signal_list5. trap -signale_list

语法说明:

语法1:-l选项用于列出当前系统支持的信号列表,和"kill -l"一样的作用。-p选项用于列出当前shell环境下已经布置好的陷阱。

语法2:当捕捉到给定的信号列表中的某个信号时,就执行此处给定cmd-body中的命令。

语法3:命令参数为空字符串,这时shell进程和shell进程内的子进程都会忽略信号列表中的信号。

语法4:省略命令参数,重置陷阱为启动shell时的陷阱。不建议此语法,当给定多个信号时结果会出人意料。

语法5:等价于语法4。

trap不接任何参数和选项时,默认为"-p"。

(1).查看当前shell已布置的陷阱。

[root@linuxidc ~]# trap

trap-- ''SIGTSTP

trap-- ''SIGTTIN

trap-- '' SIGTTOU

这3个陷阱都是信号忽略陷阱,当捕获到TSTP、TTIN或TTOU信号时,将不做任何处理。

(2).设置一个可以忽略CTRL+C和15信号的陷阱。

[root@linuxidc ~]# trap ''SIGINT SIGTERM

[root@linuxidc~]# trap

trap-- ''SIGINT

trap-- ''SIGTERM

trap-- ''SIGTSTP

trap-- ''SIGTTIN

trap-- '' SIGTTOU

这样一来,当前的shell就无法被kill -15杀死。

[root@linuxidc ~]# kill $BASHPID;echo killcurrent bash failedkill current bash failed

(3).设置一个陷阱,当这个陷阱捕捉到15信号时,就打印一条消息。

[root@linuxidc ~]# trap 'echo caught the TERM signal'TERM

[root@linuxidc~]# kill$BASHPID

caught the TERM signal

再查看已设置的陷阱,之前设置为忽略TERM信号的陷阱已经被覆盖。

[root@linuxidc ~]# trap

trap-- ''SIGINT

trap-- 'echo caught the TERM signal'SIGTERM

trap-- ''SIGTSTP

trap-- ''SIGTTIN

trap-- '' SIGTTOU

(4).重置针对INT和TERM这两个信号的陷阱为初始状态。

[root@linuxidc ~]# trap -SIGINT SIGTERM

[root@linuxidc~]# trap

trap-- ''SIGTSTP

trap-- ''SIGTTIN

trap-- '' SIGTTOU

(5).在脚本中设置一个能忽略CTRL+C和SIGTERM信号的陷阱。

[root@linuxidc ~]# cat trap1.sh#!/bin/bash

# script_name: trap1.sh#

trap''SIGINT SIGTERMsleep 10

echo sleep success

当执行该脚本后,将首先陷入睡眠状态,按下CTRL+C将无效。仍会执行完所有的命令。

[root@linuxidc ~]# ./trap1.sh

^C^C^C^Csleep success

(6).布置一个当脚本中断时能清理垃圾并退出立即脚本的陷阱。

[root@linuxidc ~]# cat trap1.sh#!/bin/bash

# script_name: trap1.sh#

trap'echo trap handling...;rm -rf /tmp/$BASHPID$BASHPID;echo TEMP file cleaned;exit'SIGINT SIGTERM SIGQUIT SIGHUPmkdir -p /tmp/$BASHPID$BASHPID/

touch /tmp/$BASHPID$BASHPID/{a.txt,a.log}sleep 10

echo first sleepsuccesssleep 10

echo second sleep success

这样,无论是什么情况中断(除非是SIGKILL),脚本总能清理掉临时垃圾。

1.3 布置完美陷阱必备知识

(1).陷阱的守护对象是shell进程本身,不会守护shell环境内的子进程。但如果是信号忽略型陷阱,则会守护整个shell进程组使其忽略给定信号。

以下面这个脚本为例,设置的陷阱会捕捉到SIGING和SIGTERM两个信号,捕捉到信号时将输出陷阱做出处理的时间点。

[root@linuxidc ~]# cat trap2.sh#!/bin/bash

# script_name: trap2.sh#

trap'echo trap_handle_time: $(date +"%F %T")'SIGINT SIGTERMecho time_start: $(date +"%F %T")sleep 10

echo time_end1: $(date +"%F %T")sleep 10

echo time_end2: $(date +"%F %T")

执行该脚本,并另开一个会话窗口,杀死trap2.sh脚本。

[root@linuxidc ~]# ./trap2.sh[root@linuxidc~]# killall -s SIGTERM trap2.sh

执行结果如下。

time_start: 2017-08-14 12:59:23trap_handle_time:2017-08-1412:59:33time_end1:2017-08-14 12:59:33time_end2:2017-08-14 12:59:43

结果中的trap_handle_time证明,脚本所在shell进程收到SIGTERM信号后,trap成功进行了处理。如果细心的话,会发现trap处理的时间正好是10秒之后,这并不是因为正好10秒之后才发送SIGTERM信号,而是因为trap就是这么工作的,这是另一个需要注意的点,稍后见下文的(2)。

再次执行脚本,在另个会话窗口下杀死脚本中正在运行的sleep进程和trap2.sh脚本所在进程。

[root@linuxidc ~]# ./trap2.sh[root@linuxidc~]# killall -s SIGTERM sleep ;sleep 3; killall -s SIGINT trap2.sh # 另一个会话终端下执行此命令

最终将返回如下结果:

time_start: 2017-08-14 12:23:06Terminated # 接收到对sleep发送的SIGTERM信号

time_end1:2017-08-14 12:23:09# 没有trap_handle_time,陷阱没有守护sleep进程

trap_handle_time:2017-08-14 12:23:19# shell进程本身收到了SIGINT信号,并被陷阱处理了

time_end2:2017-08-14 12:23:19

结果说明脚本中的trap陷阱没有守护shell内的sleep进程,只守护了shell本身。同样也发现了,虽然是在3秒后发送INT信号给脚本进程,但陷阱同样是在10秒之后才开始处理的。

再修改脚本中的陷阱为信号忽略陷阱。

[root@linuxidc ~]# cat ./trap3.sh#!/bin/bash

# script_name: trap3.sh#

trap''SIGINT SIGTERMecho time_start: $(date +"%F %T")sleep 10

echo time_end1: $(date +"%F %T")sleep 10

echo time_end2: $(date +"%F %T")

执行trap3.sh,并在另一个会话终端下杀死sleep进程。

[root@linuxidc ~]# ./trap3.sh[root@linuxidc~]# killall -s SIGTERM sleep;sleep 3;killall -s SIGINT sleep # 另一个会话终端下执行此命令

结果如下。从时间差可以看出,无论是SIGTERM还是SIGINT信号,sleep进程都被忽略型trap守护了。

time_start: 2017-08-14 12:31:54time_end1:2017-08-14 12:32:04time_end2:2017-08-14 12:32:14

(2).如果shell中针对某信号设置了陷阱,则该shell进程接收到该信号时,会等待其内正在运行的命令结束才开始处理陷阱。

其实(1)中的几个示例的结果已经证明了这一点。只要是向shell进程发送的信号,都会等待当前正在运行的命令结束后才处理信号,然后继续脚本向下运行。

(3).CTRL+C和SIGINT不是等价的。当某一时刻按下CTRL+C,它是在向整个当前运行的进程组发送SIGINT信号。对shell脚本来说,SIGINT不仅发送给shell脚本进程,还发送给脚本中当前正在运行的进程。

所以,如果shell中设置SIGINT陷阱,不仅会终止脚本中当前正在运行的进程,trap还会立即进行对应的处理。

以下面的脚本trap4.sh为例。

[root@linuxidc ~]# cat trap4.sh#!/bin/bash

# script_name: trap4.sh#

trap'echo trap_handle_time: $(date +"%F %T")'SIGINTecho time_start: $(date +"%F %T")sleep 10

echo time_end1: $(date +"%F %T")sleep 10

echo time_end2: $(date +"%F %T")

如果使用kill命令向trap4.sh发送信号,正常情况下trap会在当前运行的sleep进程完成后才进行相关处理。但如果是按下CTRL+C,先看结果。

[root@linuxidc ~]# ./trap4.shtime_start:2017-08-14 13:41:30

^Ctrap_handle_time: 2017-08-14 13:41:31time_end1:2017-08-14 13:41:31

^Ctrap_handle_time: 2017-08-14 13:41:32time_end2:2017-08-14 13:41:32

结果中显示,两次按下CTRL+C后,不仅sleep立刻结束了,trap也立即进行处理了。这说明CTRL+C不仅让脚本进程收到了SIGINT信号,也让当前正在运行的进程收到了SIGINT信号。

需要特别说明的是,如果当前正在运行的进程处在循环内,当该进程收到了终止进程后,仅仅只是立即终止当次进程,而不会终止整个循环,也就是说,它还会继续向下执行后续命令并进入下一个循环。如果此时是使用CTRL+C发送SIGINT,则每次CTRL+C时,trap也会一次次进行处理。

注意点(1)(2)(3)很重要,因为搞清楚了它们,才能明白脚本中当前正在运行的进程是先完成还是立即结束,这在写复杂脚本或任务型脚本极其重要。例如大量文档中www.example.com需要替换成www.example.net,假如使用sed进行处理,我们肯定不希望替换了一部分文件的时候被临时终止。

(4).每个陷阱都有守护范围。每一个陷阱只将守护它后面的所有进程,直到遇到下一个相同信号的陷阱。

以shell脚本为例,如下图所示。

(5).当shell环境下设置了信号忽略陷阱时,子shell在启动时将继承该陷阱,且这些信号忽略陷阱不可再改变或重置。信号忽略陷阱是子shell唯一继承的陷阱类型。

先在当前shell环境下设置一个忽略SIGINT的陷阱,和一个不忽略SIGTERM的陷阱。

[root@linuxidc ~]# trap ''SIGINT

[root@linuxidc~]# trap 'echo haha' SIGTERM

以下是测试脚本。脚本中首先输出脚本刚启动时的最初陷阱列表,随后修改陷阱并输出新的陷阱列表,最后重置陷阱并输出重置后的陷阱列表。

[root@linuxidc ~]# cat trap6.sh#!/bin/bash

# script_name: trap6.sh

echo old_trap:--------trap-p

trap'echo haha'SIGINT SIGTERMecho new_trap:--------trap-pecho "reset trap:------"trap-SIGINT SIGTERM

trap-p

执行结果如下。

[root@linuxidc ~]# ./trap6.shold_trap:--------trap-- ''SIGINT

new_trap:--------trap-- ''SIGINT

trap-- 'echo haha'SIGTERM

reset trap:------trap-- '' SIGINT

从结果中可以看出,启动脚本时,父shell中忽略SIGINT的陷阱被继承了,但不忽略信号的陷阱未被继承。而且脚本继承的信号忽略陷阱无法被修改和重置。

(6).交互式的shell下,如果没有定义任何SIGTERM信号的陷阱,则会忽略该信号。

所以,在默认(未定义SIGTERM陷阱)时,无法直接通过15信号杀死当前bash进程。

[root@linuxidc ~]# kill $BASHPID;echo passed;kill -9$BASHPID

passed

# 此处当前bash已被kill-9强制杀死

(7).除了kill -l或trap -l列出的信号列表,trap还有4种特殊的信号:EXIT(或信号代码0)、ERR、DEBUG和RETURN。DEBUG和RETURN这两种信号陷阱无需关注。

EXIT信号也是0信号,当设置了EXIT陷阱时,每次exit的时候都会被捕捉,并做相关处理。

ERR陷阱是在设置了"set -e"时生效的,当设置了"set -e"选项,每次遇到非0退出状态码时会退出当前shell,如果写在脚本中,就是退出脚本。有了它就不用再在脚本中书写对"$?"是否(不)等于0的判断语句,不过它主要用于避免脚本中产生错误时,错误被滚雪球式的不断放大。很多人将这一设置当作写shell脚本的一项行为规范,但我个人不完全认同,很多时候非0退出状态码是无关紧要的,甚至有时候非0状态码才是继续执行的必要条件。

回到话题上。先看看"set -e"的效果。以下面的脚本为例,在脚本中,mv命令少给了一个参数,它是错误命令,返回的是非0状态码。

[root@linuxidc ~]# vim trap8.sh#!/bin/bash

set-eecho "right here"

mv ~/a.txt

["$?" -eq 0 ] && echo "right again" || echo "wrong here"

如果不设置"set -e",那么会被下一条语句判断,但因为设置了"set -e",使得在mv错误发生时,就立即退出脚本所在的shell。也就是说,对"$?"的判断语句根本就是多余的。结果如下。

[root@linuxidc ~]# ./trap8.shright heremv: missing destination file operand after ‘/root/a.txt’

Try'mv --help' for more information.

可以设置ERR陷阱,专门捕获"set -e"起作用时的信号。例如,当命令错误时,做一些临时文件清理动作等。注意,当捕获到了ERR信号时,脚本不会再继续向下运行,而是trap处理结束后就立即退出。例如:

[root@linuxidc ~]# vim trap8.sh#!/bin/bash

set-e

trap'echo continue'ERRecho "right here"

mv ~/a.txt

["$?" -eq 0 ] && echo "right again" || echo "wrong here"

echo haha

执行结果如下:

[root@linuxidc ~]# ./trap8.shright heremv: missing destination file operand after ‘/root/a.txt’

Try'mv --help' for moreinformation.

continue

(8).在trap中两个很好用的变量:BASH_COMMAND和LINENO。BASH_COMMAND变量记录的是当前正在执行的命令行,如果是用在陷阱中,则记录的是陷阱触发时正在运行的命令行。LINENO记录的是正在执行的命令所处行号。

例如:

[root@linuxidc ~]# vim trap8.sh#!/bin/bash

set-e

trap'echo "error line: $LINENO,error cmd: $BASH_COMMAND"'ERRecho "right here"

mv ~/a.txt

执行结果。

[root@linuxidc ~]# ./trap8.shright heremv: missing destination file operand after ‘/root/a.txt’

Try'mv --help' for moreinformation.

error line:5,error cmd: mv ~/a.txt

(9).处理脚本中启动的后台进程。

通常trap在脚本中的作用之一是在突然被中断时清理一些临时文件然后退出,虽然它会等待脚本中当前正在运行的命令结束,然后清理并退出。但是,很多时候会在脚本中使用后台进程,以加快脚本的速度。而后台进程是独立挂靠在init/systemd下的,所以它不受终端以及shell环境的影响。换句话说,当脚本突然被中断时,即使陷阱捕获到了该信号,并清理了临时文件后退出,但是那些脚本中启动的后台进程还会继续运行。

这就给脚本带来了一些不可预测性,一个健壮的脚本必须能够正确处理这种情况。trap可以实现比较好的解决这种问题,方法是在trap的命令行中加上向后台进程发送信号的语句,然后再退出。

以下面的脚本为例。

[root@linuxidc ~]# vim trap10.sh#!/bin/bash

trap'echo first trap $(date +"%F %T");exit'SIGTERMecho first sleep $(date +"%F %T")sleep 20 &

echo second sleep $(date +"%F %T")sleep 5

该脚本中首先将一个sleep放入后台运行。正常情况下,该脚本执行5秒后就会退出,但在20秒后后台进程sleep才会结束,即使突然发送中断信号TERM触发trap也一样。

于是现在的目标是,在sleep 5的过程中突然中断脚本时,能杀死后台sleep进程。可以使用"!"这个特殊变量。修改后的脚本如下。

[root@linuxidc ~]# vim trap10.sh#!/bin/bash

trap'echo first trap $(date +"%F %T");kill $pid;exit'SIGTERMecho first sleep $(date +"%F %T")sleep 20 &pid="$!"

sleep 30 &pid="$! $pid"

echo second sleep $(date +"%F %T")sleep 5

执行该脚本,并在另一个会话窗口发送SIGTERM信号给该脚本进程。

[root@linuxidc ~]# ./trap10.sh ; ps aux | grep sleep[root@linuxidc~]# kill trap10.sh # 另一个会话窗口执行

执行结果如下。可见sleep被正常终止。

first sleep 2017-08-14 21:29:19secondsleep 2017-08-14 21:29:19first trap2017-08-14 21:29:24root69096 0.0 0.0 112644 952 pts/0 S+ 21:29 0:00 grep --color=auto sleep

linux捕捉信号sigint失败,为shell布置陷阱:trap捕捉信号方法论相关推荐

  1. Linux服务器升级GLIBC失败导致shell不可用的问题解决经历

    问题发生 试图通过编译安装升级Linux服务器的glibc库版本,install失败以后,shell中的大部分命令(ls,cat,rm,cp,ln,scp,vi,yum等)都执行报错,尝试新的ssh连 ...

  2. linux虚拟用户登录失败,用shell用户或虚拟用户登录pureftpd

    摘要:讲解了Unix系统下pureftpd如何用shell用户登录,以及如何用虚拟用户登录. 目录: 一.系统真实(shell)用户登录 二.puredb虚拟用户支持 三.技巧 ----------- ...

  3. linux shell trap捕捉信号 附信号表 SIGTERM SIGKILL

    trap捕捉信号有三种形式 第一种: trap "commands" signal-list 当脚本收到signal-list清单内列出的信号时,trap命令执行双引号中的命令. ...

  4. apscheduler 脚本执行失败_在脚本中使用 Bash 信号捕获 | Linux 中国

    无论你的脚本是否成功运行,信号捕获(trap)都能让它平稳结束. 来源:https://linux.cn/article-12715-1.html 作者:Seth Kenlon 译者:Hank Cho ...

  5. linux signal函数用法,linux信号机制之sigaction构造体浅析,signal 函数,信号捕捉.

    来自:http://hi.baidu.com/phenix_yw/blog/item/6eb4ca391d1479f23a87ce19.html 信号安装函数sigaction(int signum, ...

  6. shell中trap捕捉到信号的处理

    2019独角兽企业重金招聘Python工程师标准>>> 一. trap捕捉到信号之后,可以有三种反应方式: (1)执行一段程序来处理这一信号 (2)接受信号的默认操作 (3)忽视这一 ...

  7. linux脚本登录启动失败,在Linux上检查用户登录成功与失败的shell脚本

    在Linux上检查用户登录成功与失败的shell脚本 Linux管理员的典型任务之一是检查Linux系统中成功和失败的登录尝试. 手动验证它们非常困难,因为" /var/log/secure ...

  8. 刨根问底:linux中bash shell中SIGHUP和SIGTERM信号的处理

    新人最容易发生的事情就是用ssh客户端(比如xshell)登录服务器之后,用&在后台执行了命令,第二天兴致勃勃去看任务,发现已经被kill了.一开始我学到的知识是,运行的后台命令都需要nohu ...

  9. Linux系统编程【文件IO、进程、进程间通信、信号、线程、互斥】

    linux系统编程 个人通过学习,手打了一份48000字的Linux系统编程的笔记,包含了[文件IO.进程.进程间通信.信号.多线程.互斥]等知识点,并给出了大量的代码案例对每个重要的知识点进行了代码 ...

最新文章

  1. 清华刘知远组:​让预训练语言模型持续高效吸收新领域知识 | ACL 2022
  2. 是时候重新定义安全了,阿里云肖力解读安全责任共担模型
  3. ip rule,ip route,iptables 三者之间的关系
  4. InstallShield 购买价格、教程培训、销售服务——中国独家总代Xlsoft China
  5. 关于 uniqueidentifier
  6. 如何在 Linux 中启用 Shell 脚本的调试模式
  7. 安装OpenResty,实现分发层、应用层nginx+lua开发(附加问题:bad argument #2 to ‘set_keepalive‘ (number expected, got nil)
  8. 解决针对ubuntu11.04安装中文包后不能正常查看或使用pdf和Archiver的问题
  9. 创维oled工厂模式abd_创维电视五大新品释放创新社交属性,让新年「大有可玩」...
  10. VSTO学习笔记(二)Excel对象模型
  11. C++ 多字节与宽字符串的相互转换
  12. ubuntu mysql主从库的搭建
  13. 上下五千年,人口十几亿。在这遇见你,缘份真神奇
  14. 数仓(五):数据建模--ER模型/维度建模,概念模型/逻辑模型/ 物理模型
  15. 使用python调整excel表格的行和列以及合并或拆分单元格
  16. 2019 年第 26 周 DApp 影响力排行榜 | TokenInsight
  17. SVN项目提交设置忽略上传资源
  18. VMware14虚拟机安装苹果系统
  19. 《基础水文数据库》应用软件-水文预报中PA值计算
  20. 高德地图+Echarts+Vue

热门文章

  1. 论文浅尝 | KGQR: 用于交互式推荐的知识图谱增强Q-learning框架
  2. 论文浅尝 - WSDM20 | 基于弱监督及逐步推理的多关系知识图谱问答
  3. 专享!解读抖音企业蓝V认证的详细流程是怎样的
  4. (转)理解MySQL——索引与优化
  5. 编译过程中的链接地址对最终编译镜像文件的影响
  6. 操作系统(二): 进程与线程
  7. WP7开发小技巧之快捷键
  8. vue-day04-vue前端交互
  9. mybatis学习笔记-01什么是mybatis
  10. java将一个对象赋值给另一个对象_java一个对象赋值给另一个对象,支持平铺类和层级类间的互转...