我为我发布的答案使用了更好的测试用例。我在这里添加了更新的测试用例,以防有人想进一步试验:
#!/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保护我的脚本免受信号的影响(并安装正确的关机序列)?
信号首先会消除记录,所以我无法捕捉主要过程中的死线...
(我在问题的最后添加了更多的想法和分析。)
这将是一个很长的问题,但我认为发布我已经完成的工作将有助于理解正在发生的事情:
TEST SETUP 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
TEST SETUP 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
。我coredumped它,但无法找到它,它来自哪里。它似乎在某种程度上取决于主陷阱中的回声是否重定向到/dev/stderr
(变体1 )或不是(变体2 )。
在Ctrl+C
之后,通常"trapped 2"
首先被激活,很少"end 2"
。这表明(与我最初的看法相反),处理信号时不涉及过程层次结构。正在运行的进程(复合语句,2个进程替换,h和h2子shell,sleep
进程,cat
进程)并行运行,并且在信号发生时正好运行已交付,将处理它。出于某种原因,主要是stderr重定向的进程替换。我认为cat
是主接收器,它没有安装信号处理程序,所以它就死了(这就是为什么我尝试添加2 cat
s,所以第二个可以保持子shell运行)。
这是重点,我没有真正的线索,会发生什么。 (我甚至都不知道,如果到目前为止我做到了......)
我认为,信号将从cat
传播到其包含的进程,进程替换bash shell,其中安装了信号处理程序,并打印"trapped 2"
。
现在,我想,故事会在这里结束,一个戒指被Isildur摧毁,佛罗多留在家里......但不是。它以某种方式起泡,并设法杀死sleep
。即使有2 cat
s,所以如果一个被销毁,则子shell保持活动状态。我发现很可能SIGPIPE
是杀死睡眠的东西,因为没有捕获它,我看到的行为与我在这里发布的行为不同。但有趣的是,似乎我需要在每个位置trap
SIGPIPE
,而不仅仅是在睡眠子shell中,或者再次显示不同的行为。
我猜,SIGPIPE
信号到达sleep
,杀死它,因此复合语句中只剩下echo
,执行,子shell完成。 stdout重定向的进程替换也被杀死的复合语句/函数shell杀死了,可能是另一个SIGPIPE
?
更有趣的是,有时根本没有显示"trapped 1"
。
奇怪的是,我没有看到50%"trapped 2"
和50%"trapped 1"
。
请记住,我的目标是有序关闭系统/服务/脚本。
1)首先,正如我所看到的,如果" 业务流程",此处由sleep
/ cat
表示不有自己的信号处理,trap
没有任何数量可以防止它们被杀死。
2)信号处理程序不是继承的,每个子shell都必须有自己的陷阱系统。
3)没有什么比一个进程组能够以公共方式处理信号,无论哪个进程信号发生罢工就会发挥作用,并且那里被杀死的进程的结果可能会在进程树中进一步传播。
但是,我不清楚,如果一个进程无法处理一个信号,它会将它抛给它的包含shell吗?或者它是另一个信号,交付什么?肯定会有事情发生,否则信号处理程序就不会被触发。
在/我的理想世界中,trap
会保护安装它的shell中的任何内容不接收信号,因此sleep
- s,cat
- s将是通过指定的清理函数关闭:杀死sleep
,其余的将记录其最后一行,然后按 - 而不是:所有日志记录都被清除,只有在此之后才会主要过程最终被杀死......
我错过了一些微不足道的事情吗?设置-o魔术?只是继续添加更多的陷阱,直到它突然起作用?
Ctrl+C
之后信号如何真正传播?
SEGFAULT
来自哪里?
最重要的是:
从记录开始,我可以保护这个结构免受信号的影响吗?或者我应该避免进程替换,并提出另一种类型的输出过滤/记录?
GNU bash,版本4.4.12(1)-release(x86_64-pc-linux-gnu)
在完成测试后,我发现了这些QA-s,我认为这可能与我的案例有关,但我不知道,我究竟该怎样才能使用它们:
How to use trap reliably using Bash running foreground child processes
Trap signal in child background process
尽管如此,我尝试用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
,仍然不知道,它在哪里来自。
答案 0 :(得分:1)
经过无数次的实验,我得出的结论是,我认为不可能做我想做的事,但我仍然不了解每一个细节。
我发布了我的调查结果,但暂时不会接受我的答案,以防万一 - 希望有人能更好地了解正在发生的事情。
看来,我犯了很多错误......
1)SEGFAULT
来自写入封闭的fd(stderr
)。但是我认为这是在bash甚至内核级别的某个地方触发的,某种竞争条件可能 - 我会假设,bash管理的进程树会在封闭的I / O的剩余虚拟内存地址上被分割出来(我怀疑,这会导致错误)。无论如何,用正确的TTY设备替换/dev/stderr
似乎可以解决这个问题。
Write to terminal after redirecting stdout to a file without using stderr?
echo or print /dev/stdin /dev/stdout /dev/stderr
Portability of “> /dev/stdout”
2)在记录进程之前停止日志的整个问题来自于它们都在前台进程组中。在Ctrl+C
上,终端会在fg进程组中向{em>进程每个进程提供一个SIGINT
。在打印过程树之后结果显示,记录器过程是阵列中的第一个打印过程,因此可能它们是第一个要交付并处理SIGINT
的过程。
How does Ctrl-C terminate a child process?
How to make a program reading stdin run in background on linux?
Control which process gets cancelled by Ctrl+C
3)产生进程的shell无法控制信号传递,事实上它正在等待,因此无法在该shell中设置一些魔法以保护由cat
启动的内容。没有安装信号处理程序的shell。
4)看到问题来自fg进程组中的所有进程,很明显将不必要的进程移到后台就是解决方案,如:
2> >( cat & )
不幸的是,在这种情况下,没有输出传递给cat
,而是立即终止。
我怀疑,这与后台工作获得SIGSTOP
有关,如果stdin
在后台工作时已打开。
Writing to stdin of background process
Linux process in background - “Stopped” in jobs?
Why is SIGINT not propagated to child process when sent to its parent process?
注意:setsid cmd
会使cmd
在其自己的会话中启动,该会话将包含一个全新的流程组,其中仅包含cmd
,因此它可能会用于分隔记录器和记录的。我没有想到它,也没有尝试过它。
Running a process in the background with input/output redirection
Send command to a background process
Why Bash is like that: Signal propagation
How to propagate SIGTERM to a child process in a Bash script
在设置中:
{
cmd
} \
2> >(logger) \
1> >(logger)
我找不到在进程组级别将cmd
与logger
分开的好方法。对logger
进行后台操作会禁止它们接收输出,而是立即终止,可能是通过SIGSTOP
。
一种解决方案可能是使用命名管道,这将允许更好的控制,并可以分离记录和记录器进程。但是,我最初决定使用bash提供的进程替换来避免手动编码管道的复杂性。
我最终选择的方法是简单地背景整个过程树(cmd
+ logger
s),然后让另一个级别处理信号。
f {
{
cmd
} \
2> >(logger) \
1> >(logger)
}
trap ...
set -m
f &
wait
我意识到简单的后台操作是不够的,因为非交互式shell(从文件运行脚本)不会在单独的进程组中运行后台进程。为此,最简单的选择是将shell设置为交互模式:set -m
。 (我希望这不会导致更新的问题,到目前为止似乎很好。)
注意:setsid
不适用于函数,因此主脚本需要自己的文件,并从第二个脚本文件开始。
Prevent SIGINT from interrupting function call and child process(es) within