谷动谷力

 找回密码
 立即注册
查看: 3174|回复: 0
打印 上一主题 下一主题
收起左侧

【Shell脚本】Shell脚本程序开发--信号发送与捕捉

[复制链接]
跳转到指定楼层
楼主
发表于 2023-8-26 14:24:58 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
【Shell脚本】Shell脚本程序开发--信号发送与捕捉


信号(Signal)概述

信号(Signal):信号是在软件层次上对中断机制的一种模拟,通过给一个进程发送信号,执行相应的处理函数。
进程可以通过三种方式来响应一个信号:

忽略信号,即对信号不做任何处理,其中有两个信号不能忽略:SIGKILL 及 SIGSTOP。

捕捉信号,修改捕捉到的信号的缺省操作。

执行缺省操作,Linux 对每种信号都规定了默认操作。

Linux 究竟采用上述三种方式的哪一个来响应信号呢?取决于传递给响应的 API 函数。

CoreDump(核心转储):当程序运行过程中异常退出时,内核把当前程序在内存状况存储在一个core 文件中,以便调试。执行命令 ulimit -c 如果是 0 则没有开启,也不会生成 core dump 文件,可通过 ulimit -c unlimited 命令临时开启 core dump 功能,只对当前终端环境有效,如果想永久生效,可修改/etc/security/limites.conf 文件,添加一行 "* soft core unlimited"默认生成的 core 文件保存在可执行文件所在的目录下,文件名为 core。如果想修改 core 文件保存路径,可通过修改内核参数:echo "/tmp/corefile-%e-%p-%t" > /proc/sys/kernel/core_pattern则文件名格式为 core-命名名-pid-时间戳

Linux 支持两种信号:

一种是标准信号,编号 1-31,称为非可靠信号(非实时),不支持队列,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,如果第一个信号没有处理完,第二个信号将会丢弃。
另一种是扩展信号,编号 32-64,称为可靠信号(实时),支持队列,发多少次进程就可以收到多少次。信号类型比较多,我们只要了解下,记住几个常用信号就行了,红色标记的我觉得需要记下。
  1. 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
  2. 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
  3. 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
  4. 13) SIGPIPE     14) SIGALRM     15) SIGTERM     17) SIGCHLD
  5. 18) SIGCONT     19) SIGSTOP     20) SIGTSTP     21) SIGTTIN
  6. 22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
  7. 26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO
  8. 30) SIGPWR      31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1
  9. 36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4  39) SIGRTMIN+5
  10. 40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8  43) SIGRTMIN+9
  11. 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
  12. 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13
  13. 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9
  14. 56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6  59) SIGRTMAX-5
  15. 60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2  63) SIGRTMAX-1
  16. 64) SIGRTMAX
复制代码

下面我们对编号小于SIGRTMIN(1-31)的信号详细说明。


1) SIGHUP
本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。

登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也能继续下载。

此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。


2) SIGINT
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。


3) SIGQUIT
和SIGINT类似, 但由QUIT字符(通常是Ctrl-/)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。


4) SIGILL
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。


5) SIGTRAP
由断点指令或其它trap指令产生. 由debugger使用。


6) SIGABRT
调用abort函数生成的信号。


7) SIGBUS
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。


8) SIGFPE
在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。


9) SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。


10) SIGUSR1
留给用户使用


11) SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.


12) SIGUSR2
留给用户使用


13) SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。


14) SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.


15) SIGTERM
程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。


17) SIGCHLD
子进程结束时, 父进程会收到这个信号。

如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程来接管)。


18) SIGCONT
让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符


19) SIGSTOP
停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.


20) SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号


21) SIGTTIN
当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.


22) SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到。


23) SIGURG
有"紧急"数据或out-of-band数据到达socket时产生。


24) SIGXCPU
超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。


25) SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。


26) SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间。


27) SIGPROF
类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间。


28) SIGWINCH
窗口大小改变时发出。


29) SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作。


30) SIGPWR
Power failure


31) SIGSYS
非法的系统调用。


Shell脚本程序开发--信号发送与捕捉

发送信号

Shell中发送信号可以使用下面方式:

kill -<sig> <pid> 向指定进程发送信号,sig可以是信号值或是信号名;
killall <process_name> 向所有名为process_name的进程发送SIGTERM(终止进程)信号;
pkill 可以通过指定进程名、用户名、组名、终端、UID、EUID 和 GID 等属性来杀掉相应的进程;
上面所有命令更详细内容可以使用 man 来查看。

Shell 脚本程序开发 -- 信号发送与捕捉

捕获信号

可以使用 trap 命令来捕获信号,语法如下:
  1. trap command signal
复制代码

command 可以是一个脚本或是一个函数,如果 command 填空字符就相当于捕获信号但不处理;signal 可以用信号名,也可以用信号值。

如果没有提供参数,trap 将打印与每个要捕获的信号相关联的命令的列表。

在编写调试Shell脚本时,除了前面的那些信号,还有几个信号比较实用:

信号名        产生原因
EXIT        从函数或脚本成功退出
ERR        从函数或脚本执行失败
DEBUG        每一条命令执行之前

在使用 trap 时还可以借助 LINENO 和 BASH_COMMAND 来打印触发时的行号和命令。

发送信号一般有两种情况:

一种是内核检测到系统事件,比如键盘输入 CTRL+C 会发送 SIGINT 信号。

另一种是通过系统调用 kill 命令来向一个进程发送信号。

kill 命令

格式:kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

-s # 信号名称
-n # 信号编号
-l # 打印编号 1-31 信号名称

给一个进程发送终止信号:

kill -s SIGTERM pid

kill -n 15 pid

kill -15 pid

kill -TREM pid

trap 命令

格式:trap [-lp] [[arg] signal_spec ...]

trap 命令定义 shell 脚本在运行时根据接收的信号做相应的处理。

-l            # 打印编号 1-64 编号信号名称
arg         # 捕获信号后执行的命令或者函数
signal_spec # 信号名或编号

一般捕捉信号后,做以下几个动作:

清除临时文件
忽略该信号
询问用户是否终止脚本执行

trap '触发指令' 信号
忽略信号的操作
trap '' 信号
恢复原信号的操作
trap '-' 信号
列出自定义信号操作
trap -p
当脚本退出时,执行finish函数
trap finish EXIT

set命令

set: set [–abefhkmnptuvxBCHP] [-o 选项名] [–] [参数 …]
设定或取消设定 shell 选项和位置参数的值。
改变 shell 选项和位置参数的值,或者显示 shell 变量的名称和值。
set - 选项打开
set + 选项关闭

选项:
-a 标记修改的或者创建的变量为导出。
-b 立即通告任务终结。
-e 如果一个命令以非零状态退出,则立即退出。
-f 禁用文件名生成(模式匹配)。
-h 当查询命令时记住它们的位置
-k 所有的赋值参数被放在命令的环境中,而不仅仅是命令名称之前的参数。
-m 启用任务控制。
-n 读取命令但不执行

-o 选项名
设定与选项名对应的变量:
allexport 与 -a 相同
braceexpand 与 -B 相同
emacs 使用 emacs 风格的行编辑界面
errexit 与 -e 相同
errtrace 与 -E 相同
functrace 与 -T 相同
hashall 与 -h 相同
histexpand 与 -H 相同
history 启用命令历史
ignoreeof shell 读取文件结束符时不会退出
interactive-comments允许在交互式命令中显示注释
keyword 与 -k 相同
monitor 与 -m 相同
noclobber 与 -C 相同
noexec 与 -n 相同
noglob 与 -f 相同
nolog 目前可接受但是被忽略
notify 与 -b 相同
nounset 与 -u 相同
onecmd 与 -t 相同
physical 与 -P 相同
pipefail 管道的返回值是最后一个非零返回值的命令的返回结果,或者当所有命令都返回零是也为零。
posix 改变默认时和 Posix 标准不同的 bash 行为以匹配标准
privileged 与 -p 相同
verbose 与 -v 相同
vi 使用 vi 风格的行编辑界面
xtrace 与 -x 相同

-p 无论何时当真实的有效的用户身份不匹配时打开。禁用对 $ENV 文件的处理以及导入 shell 函数。关闭此选项会导致有效的用户编号和组编号设定为真实的用户编号和组编号

-t 读取并执行一个命令之后退出。
-u 替换时将为设定的变量当作错误对待。
-v 读取 shell 输入行时将它们打印。
-x 执行命令时打印它们以及参数。
-B shell 将执行花括号扩展。
-C 设定之后禁止以重定向输出的方式覆盖常规文件。
-E 设定之后 ERR 陷阱会被 shell 函数继承。
-H 启用 ! 风格的历史替换。当 shell 是交互式的时候这个标识位默认打开。
-P 设定之后类似 cd 的会改变当前目录的命令不追踪符号链接。
-T 设定之后 DEBUG 陷阱会被 shell 函数继承。
– 任何剩余的参数会被赋值给位置参数。如果没有剩余的参数,位置参数不会被设置。
- 任何剩余的参数会被赋值给位置参数。
-x 和 -v 选项已关闭。

使用 + 而不是 - 会使标志位被关闭。标志位也可以在shell 被启动时使用。当前的标志位设定可以在 $- 变量中找到。剩余的 ARG 参数是位置参数并且是按照$1, $2, … $n 的顺序被赋值的。如果没有给定 ARG参数,则打印所有的 shell 变量。

信号发送与捕捉 Shell脚本示例

示例1 :循环打印数字,按 CTRL+C 退出,并打印退出提示
  1. #!/bin/bash
  2. trap "echo 'exit...';exit" 2
  3. for i in {1..10}; do
  4.     echo $i
  5.     sleep 1
  6. done
复制代码

示例2 捕捉到错误由命令中退出脚本
  1. err_handler() {
  2.     ret=$?
  3.     [ "$ret" -eq 0 ] && return
  4.     echo "ERROR: Running ${FUNCNAME[1]} failed!"
  5.     echo "ERROR: exit code $ret from line ${BASH_LINENO[0]}:"
  6.     echo " $BASH_COMMAND"

  7.     exit $ret #这里退出shell
  8. }

  9. trap 'err_handler' ERR
复制代码

示例3 ctrl+C无法退出脚本
  1. trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT
  2. echo "This is a test script"

  3. count=1
  4. while [ $count -le 10 ]
  5. do
  6.     echo "Loop #$count"
  7.     sleep 1
  8.     count=$[ $count + 1 ]
  9. done

  10. echo "This is the end of the test script"
复制代码



+10
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|深圳市光明谷科技有限公司|光明谷商城|Sunshine Silicon Corpporation ( 粤ICP备14060730号|Sitemap

GMT+8, 2024-12-27 07:19 , Processed in 0.100195 second(s), 44 queries .

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表