更新
我为我发布的答案使用了一个更好的测试用例。我在这里添加更新的测试用例,以防有人想进一步试验:
#!/bin/bash
mypts="$( tty )"
# main traps
trap "echo 'trapped SIGCHLD' >$mypts" SIGCHLD
trap "echo 'trapped SIGHUP' >$mypts" SIGHUP
trap "echo 'trapped SIGINT' >$mypts" SIGINT
trap "echo 'trapped SIGPIPE' >$mypts" SIGPIPE
trap "echo 'trapped SIGSEGV' >$mypts" SIGSEGV
trap "echo 'trapped SIGSYS' >$mypts" SIGSYS
trap "echo 'trapped SIGTERM' >$mypts" SIGTERM
function h4 {
# function traps
# these mask the main traps
#trap "echo 'trapped h4 SIGCHLD'" SIGCHLD
#trap "echo 'trapped h4 SIGHUP'" SIGHUP
#trap "echo 'trapped h4 SIGINT'" SIGINT
#trap "echo 'trapped h4 SIGPIPE'" SIGPIPE
#trap "echo 'trapped h4 SIGSEGV'" SIGSEGV
#trap "echo 'trapped h4 SIGSYS'" SIGSYS
#trap "echo 'trapped h4 SIGTERM'" SIGTERM
{
# compound statement traps
# these mask the function traps
#trap "echo 'trapped compound SIGCHLD'" SIGCHLD
#trap "echo 'trapped compound SIGHUP'" SIGHUP
#trap "echo 'trapped compound SIGINT'" SIGINT
#trap "echo 'trapped compound SIGPIPE'" SIGPIPE
#trap "echo 'trapped compound SIGSEGV'" SIGSEGV
#trap "echo 'trapped compound SIGSYS'" SIGSYS
#trap "echo 'trapped compound SIGTERM'" SIGTERM
echo begin err 1>&2
echo begin log
# enable one of sleep/while/find
#sleep 63
#while : ; do sleep 0.1; done
find ~ 2>/dev/null 1>/dev/null
echo end err 1>&2
echo end log
} \
2> >(
trap "echo 'trapped 2 SIGCHLD' >$mypts" SIGCHLD
trap "echo 'trapped 2 SIGHUP' >$mypts" SIGHUP
trap "echo 'trapped 2 SIGINT' >$mypts" SIGINT
trap "echo 'trapped 2 SIGPIPE' >$mypts" SIGPIPE
trap "echo 'trapped 2 SIGSEGV' >$mypts" SIGSEGV
trap "echo 'trapped 2 SIGSYS' >$mypts" SIGSYS
trap "echo 'trapped 2 SIGTERM' >$mypts" SIGTERM
echo begin 2 >$mypts
awk '{ print "processed by 2: " $0 }' >$mypts &
wait
echo end 2 >$mypts
) \
1> >(
trap "echo 'trapped 1 SIGCHLD' >$mypts" SIGCHLD
trap "echo 'trapped 1 SIGHUP' >$mypts" SIGHUP
trap "echo 'trapped 1 SIGINT' >$mypts" SIGINT
trap "echo 'trapped 1 SIGPIPE' >$mypts" SIGPIPE
trap "echo 'trapped 1 SIGSEGV' >$mypts" SIGSEGV
trap "echo 'trapped 1 SIGSYS' >$mypts" SIGSYS
trap "echo 'trapped 1 SIGTERM' >$mypts" SIGTERM
echo begin 1 >$mypts
awk '{ print "processed by 1: " $0 }' >$mypts &
wait
echo end 1 >$mypts
)
echo end fnc
}
h4
echo finish
要获取 ascii-art 进程树(在单独的终端中):
ps axjf | less
---
---
我很难理解信号是如何在 bash 中传播的,以及哪个陷阱会处理它们。
我这里有 3 个例子。每个示例都使用 2 个变体进行了测试,即任一行都未注释。这些示例是由这个伪代码构建的:
main_trap
func
compound_statement(additional_traps) > process_redirection(additional_traps)
我用这两个品种尝试了每个例子几次。我得到的结果很少,我发布了我找到的那种。
测试如下:
- 将脚本放在一个文件中
- 运行脚本文件
Ctrl+C
在脚本仍在运行时按下
注意:简单地将这些脚本复制粘贴到现有的 bash shell 中会产生与我从文件执行时得到的不同结果。为了限制这个问题的长度,我没有附上这些结果。
我的终极问题是:
我已经使用这种布局(复合语句 + 进程重定向)来运行一些代码,并过滤并保存输出。现在出于某种原因,我决定最好保护此设置不因中断而终止,但我发现很难做到这一点。我很快发现仅仅在脚本开头调用陷阱是不够的。
有什么方法可以使用 bash / trap 保护我的脚本免受信号的影响(并安装正确的关闭序列)?
信号往往会首先清除日志记录,所以我无法捕捉到主进程的垂死线......
(我在问题的末尾添加了更多的想法和分析。)
这将是一个很长的问题,但我认为发布我已经完成的工作将有助于了解正在发生的事情:
测试设置:
测试设置 1(1 只猫):
#!/bin/bash
# variation 1:
trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
# variation 2:
#trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
h {
{
echo begin
( trap "echo 'trapped inner' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
sleep 63 )
echo end
} \
2> >( trap "echo 'trapped 2' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat ) \
1> >( trap "echo 'trapped 1' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat )
echo end 2
}
h
echo finish
结果:
# variation 1:
# trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
Segmentation fault
# variation 2:
# trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Cend 2
finish
trapped 2
begin
^Ctrapped 2
end 2
finish
begin
^Ctrapped 2
Segmentation fault
测试设置 2(2 只猫):
#!/bin/bash
# variation 1:
trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
# variation 2:
#trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
h2 {
{
echo begin
( trap "echo 'trapped inner' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
sleep 63 )
echo end
} \
2> >( trap "echo 'trapped 2' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat; cat ) \
1> >( trap "echo 'trapped 1' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat; cat )
echo end 2
}
h2
echo finish
结果:
# variation 1:
# trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
end 2
finish
end
trapped 1
trapped
begin
^Ctrapped 2
end 2
finish
end
trapped
begin
^Cend 2
finish
trapped 2
end
trapped inner
trapped
trapped 1
# variation 2:
# trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
end 2
finish
trapped inner
trapped 1
trapped
end
begin
^Ctrapped 2
end 2
finish
trapped
end
trapped inner
trapped 1
begin
^Ctrapped 2
end 2
finish
trapped inner
trapped 1
trapped
end
测试设置 3(2 只猫,无睡眠子外壳):
#!/bin/bash
# variation 1:
trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
# variation 2:
#trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
h3 {
{
echo begin
sleep 63
echo end
} \
2> >( trap "echo 'trapped 2' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat; cat ) \
1> >( trap "echo 'trapped 1' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat; cat )
echo end 2
}
h3
echo finish
结果:
# variation 1:
# trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
end 2
finish
end
trapped 1
trapped
begin
^Ctrapped 2
end 2
finish
trapped 1
trapped
end
begin
^Cend 2
finish
trapped 2
trapped 1
trapped
end
begin
^Cend 2
finish
end
trapped 2
trapped 1
trapped
begin
^Cend 2
finish
trapped 2
end
trapped
trapped 1
begin
^Cend 2
finish
end
trapped 2
# variation 2:
# trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Cend 2
trapped 2
finish
trapped
end
trapped 1
begin
^Ctrapped 2
end 2
finish
trapped
end
trapped 1
begin
^Ctrapped 2
end 2
finish
trapped 1
trapped
end
我的分析:
我添加所有 3 个测试用例的主要原因是有时我得到了一个SEGFAULT
. 我对它进行了转储,但找不到它的来源。这似乎在某种程度上取决于主陷阱中的回声是否重定向到/dev/stderr
(变体 1)或不(变体 2)。
紧随其后Ctrl+C
,通常"trapped 2"
先激活,很少"end 2"
。这表明(与我最初的看法相反),处理信号时不涉及进程层次结构。正在运行的进程(复合语句、2 个进程替换,在 h 和 h2 中是子shell、sleep
进程、cat
进程)并行运行,并且在传递信号时恰好正在运行的任何一个都将处理它。出于某种原因,这主要是 stderr 重定向的进程替换。我想这cat
是主接收器,它没有安装信号处理程序,所以它就死了(这就是我尝试添加 2cat
的原因,以便第二个可以保持子外壳运行)。
这就是重点,我没有真正的线索,会发生什么。(我什至不知道,如果我做到这一点......)
我认为,信号将从它传播cat
到它的包含进程,进程替换 bash shell,它安装了一个信号处理程序,并打印"trapped 2"
.
现在,我原以为故事会到此结束,一枚戒指被伊熙尔杜摧毁,佛罗多留在家里……但没有。不知何故,它冒泡了,并设法杀死了sleep
。即使有 2 cat
s,所以如果一个被破坏,子 shell 仍然保持活动状态。我发现 a 很可能SIGPIPE
是杀死睡眠的原因,因为没有捕获它,我看到的行为与我在此处发布的行为不同。但有趣的是,似乎我需要trap
SIGPIPE
在每个位置,而不仅仅是在睡眠子外壳中,或者再次显示不同的行为。
我想,SIGPIPE
信号到达sleep
,杀死它,所以echo
复合语句中只有一个 left ,它执行,并且那个子shell 完成了。标准输出重定向的进程替换也被杀死了,可能被另一个SIGPIPE
被杀死的复合语句/函数外壳杀死?
更有趣的是,有时"trapped 1"
根本没有显示。
奇怪的是我没有看到 50%"trapped 2"
和 50% "trapped 1"
。
我可以做什么,我想要什么?
请记住,我的目标是有序关闭系统/服务/脚本。
1)首先,正如我所看到的,如果这里由/表示的“业务流程”没有自己的信号处理,那么再多的也无法挽救它们免于被杀死。sleep
cat
trap
2)信号处理程序不是继承的,每个子shell都必须有自己的陷阱系统。
3)没有什么能像进程组那样以公共方式处理信号,无论信号碰巧碰到哪个进程都会做它的事情,并且在那里被杀死的进程的结果可能会在进程树中传播得更远。
不过,我不清楚,如果一个进程不能处理一个信号,它会把它扔到它的包含外壳吗?或者是另一个信号,传递了什么?有些东西肯定会通过,否则不会触发信号处理程序。
在一个/我的理想世界中,atrap
将保护安装它的 shell 中的任何东西不接收信号,因此sleep
-s, cat
-s 将被指定的清理功能关闭:杀死sleep
,其余的将记录它的最后一个行,然后跟随- 而不是:所有日志记录都被清除,只有在那之后主进程才会最终被杀死......
我错过了一些微不足道的事情吗?设置 -o 魔法?继续添加更多的陷阱,直到它突然起作用?
问题:
之后信号如何真正传播Ctrl+C
?
从哪里来SEGFAULT
?
最重要的:
从记录开始,我可以保护这个结构不被信号夷为平地吗?或者我应该避免进程替换,并提出另一种类型的输出过滤/日志记录?
经测试:
GNU bash,版本 4.4.12(1)-release (x86_64-pc-linux-gnu)
进一步说明:
完成测试后,我发现了这些 QA-s,我认为这可能与我的案例有关,但我不知道,我究竟该如何使用它们:
不过,我尝试用 替换sleep 63
,while : ; do sleep 0.1; done
结果如下:
测试设置 1:
# (both variations)
# 1 Ctrl + C got me a SEGFAULT
begin
^Ctrapped 2
Segmentation fault
# 2 Ctrl + C got me a SEGFAULT
begin
^Ctrapped 2
^CSegmentation fault
测试设置 2:
# variation 1
# trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
trapped 1
trapped inner
^Ctrapped 2
^CSegmentation fault
# variation 2
# trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
trapped inner
trapped 1
^Ctrapped 2
Segmentation fault
begin
^Ctrapped 2
trapped inner
trapped 1
^Ctrapped 2
^CSegmentation fault
测试设置 3:
# variation 1
# trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
trapped 1
trapped
^Ctrapped 2
^CSegmentation fault
# variation 2
# trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
trapped 1
trapped
^Ctrapped 2
^CSegmentation fault
^Ctrapped 2
trapped 1
trapped
^Ctrapped 2
Segmentation fault
所以,虽然这让我能够利用 2 cat
-s,允许 2 Ctrl+C
-s,但它总是让我SEGFAULT
,仍然不知道它来自哪里。