我有以下脚本/tmp
无限期地监视目录,如果该目录中有任何文件操作,则文件名由while循环读取,文件名中的第一个a
字符替换为b
字符,并将此修改后的文件名记录到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类型的init脚本,因为我希望保持LSB兼容,我的init脚本有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) #
据strace
,killproc
发送SIGHUP
到过程4424
和4426
,但只有后者被终止.
4426
在我的例子中,这个带有PID的子shell有什么意义,即为什么它首先被创建?另外,有没有办法忽略HUP
信号?
问题的第一部分是通过shell(在本例中为Bash)在管道中运行命令的机制来解释的.
甲管是FIFO(先入先出)单向的进程间通信(IPC)信道:它允许字节在一端(被写入只写端)和来自其他(读只读结束)无需读取或写入物理文件系统.
甲管道允许两个不同的命令来通过相互通信匿名或无名(即,在文件系统中没有条目)管.
当shell执行简单命令时,该命令在shell的子进程中运行.如果没有使用作业控制,则当子进程终止时,shell将重新获得对终端的控制.
当在管道中运行两个命令时,管道中的两个命令都作为两个单独的子进程执行,这些子进程同时运行.
在Unix系统中,使用pipe(2)
系统调用创建管道,系统调用创建一个新管道并返回一对文件描述符,其中一个引用读取端,另一个引用管道的写入端.
在GNU/Linux系统上使用Bash,clone(2)
系统调用用于创建子进程.这允许子进程与其父进程共享文件描述符表,以便两个子子进程都继承匿名管道的文件描述符,以便可以读取它并且另一个可以写入它.
在您的情况下,该inotifywait
命令获得4425的PID并通过将其连接stdout
到写端的文件描述符来写入管道的只写端.
同时,管道命令的右侧获取PID,4426并且其stdin
文件描述符设置为管道的只读端的文件描述符.由于管道右侧的子shell不是外部命令,因此表示子进程的名称与其父进程的名称相同test.sh
.
有关详细信息,请参阅man 7 pipe
以下链接:
匿名管道,维基百科文章
Unix Pipeline,Wikipedia文章
信号处理
我花了很长时间(实际上是几个小时的研究)来弄清楚为什么SIGHUP信号的陷阱没有被忽略.
我的所有研究表明,clone(2)
系统调用创建的子进程也应该能够共享父进程的信号处理程序表.
Bash手册页也说明了这一点
命令替换,用括号分组的命令和异步命令在子shell环境中调用,该shell环境是shell环境的副本,除了shell捕获的陷阱被重置为shell在调用时从其父级继承的值.
它后来说明了这一点
进入shell时忽略的信号不能被捕获或重置.当创建一个被捕获的信号时,未被忽略的信号将在子shell或子shell环境中重置为其原始值.
这表明子shell不继承未被忽略的信号处理程序.正如我所理解的那样,你的trap ':' HUP
行意味着(有效地)忽略了SIGHUP信号(因为:
内置函数除了返回成功之外什么都不做) - 而且应该被管道的子shell忽略.
但是,我最终trap
在Bash手册页中找到了内置的描述,它通过忽略定义了Bash的含义:
如果arg是空字符串,则shell将忽略每个sigspec指定的信号以及它调用的命令.
只需更改trap
命令以trap '' HUP
确保忽略SIGHUP信号,脚本本身 - 以及任何子shell.