在使用管道命令的Bash脚本中忽略HUP信号

9

我有以下脚本,无限期地监视/tmp目录,如果这个目录中有任何文件操作,那么文件名将被while循环读取,并用b字符替换文件名中的第一个a字符,然后将修改后的文件名记录在test.log文件中:

#!/bin/bash

trap ':' HUP
trap 'kill $(jobs -p)' EXIT

/usr/local/bin/inotifywait -q -m /tmp --format %f | 
  while IFS= read -r filename; do
    echo "$filename" | sed 's/a/b/' > test.log
  done

这是实际脚本的简化版本。我还有一个Sys-V类型的初始化脚本,因为我想保持LSB兼容性,所以我的初始化脚本有"force-reload"(如果服务支持,则重新加载配置。否则,服务将被重启。)选项,它会向脚本发送HUP信号。在执行"force-reload"之前,会执行"killproc -HUP test.sh",此时"pstree"的输出如下:
# pstree -Ap 4424
test.sh(4424)-+-inotifywait(4425)
              `-test.sh(4426)
# 

执行 strace killproc -HUP test.sh 后,子 shell 被终止:
# pstree -Ap 4424
test.sh(4424)---inotifywait(4425)
# 

根据stracekillproc 发送 SIGHUP 信号给进程 44244426,但只有后者被终止。

在我的例子中,PID为4426 的子shell 的目的是什么,即为什么首先创建它?此外,是否有一种方式来忽略 HUP 信号?


我已经编辑了你的问题,包括相关标签和管道命令的使用。感谢赏金带来的额外声望。在许多以前的场合,我花费了很多时间撰写一个好的答案来回答我认为有趣的问题(比如这个问题) - 只是为了发现答案被大部分人忽略。看到我的努力得到一些赞赏是鼓舞人心的。 :) - Anthony Geoghegan
1个回答

10

管道命令在子shell中运行

你的问题的第一部分是通过一个shell(在这种情况下是Bash)运行管道命令的机制来解释的。

管道是一种先进先出的单向进程间通信(IPC)通道:它允许字节被写入其中一端(只写端),从另一端(只读端)读取而无需读写物理文件系统。

管道线允许两个不同的命令通过一个匿名的未命名的(即没有文件系统条目)管道进行通信。

当shell执行简单命令时,该命令在shell的子进程中运行。如果没有使用作业控制,当子进程终止时,shell将重新获得控制终端的控制权。

当两个命令在管道中运行时,管道中的两个命令都作为两个独立的子进程同时运行。

在Unix系统中,使用pipe(2)系统调用创建管道,它创建一个新的管道并返回一对文件描述符,其中一个引用管道的读取端,另一个引用写入端。

在GNU/Linux系统上使用Bash时,使用clone(2)系统调用创建子进程。这使得子进程可以与其父进程共享文件描述符表,以便两个子进程都继承匿名管道的文件描述符,以便一个进程可以从中读取,另一个进程可以向其中写入。

在你的情况下,inotifywait命令获取PID为4425,并通过连接其stdout到写端的文件描述符来写入管道的写入端。

同时,管道命令的右侧获得PID 4426,并将其stdin文件描述符设置为管道的只读端。由于管道命令的右侧子shell不是外部命令,表示子进程的名称与其父进程相同,即test.sh

有关更多信息,请参见man 7 pipe和以下链接:

信号处理

我花了很长时间(实际上是几个小时的研究)才弄清楚为什么SIGHUP信号的陷阱没有被忽略。

所有的研究都表明,由clone(2)系统调用创建的子进程也应该能够共享父进程的信号处理程序表。
Bash手册还指出:
命令替换、用括号分组的命令和异步命令在一个子shell环境中被调用,该环境是shell环境的副本,但被shell捕获的陷阱被重置为shell从其启动父级继承的值。
它后来又说:
进入shell时忽略的信号不能被捕获或重置。在创建子shell或子shell环境时,未被忽略的已捕获信号会被重置为其原始值。
这表明子shell不会继承未被“忽略”的信号处理程序。据我所理解,您的trap ':' HUP行意味着SIGHUP信号(实际上)被忽略了(因为:内置函数除了返回成功之外什么也不做),从而应该被管道的子shell忽略。
然而,我最终发现了Bash手册中关于trap内置函数的描述,它定义了Bash所谓的“忽略”:
如果arg是空字符串,则每个sigspec指定的信号由shell和它调用的命令忽略。
简单地将trap命令更改为trap '' HUP可确保脚本本身和任何子shell都忽略SIGHUP信号。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接