【发布时间】:2018-04-11 06:44:10
【问题描述】:
更新
我为我发布的答案使用了一个更好的测试用例。我在这里添加更新的测试用例,以防有人想进一步试验:
#!/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 中会产生与我从文件执行时得到的不同结果。为了限制这个问题的长度,我没有附上这些结果。
我的终极问题是:
我已经使用这种布局(复合语句+进程重定向)来运行一些代码,并过滤并保存输出。现在出于某种原因,我决定最好保护此设置不因中断而终止,但我发现很难做到这一点。我很快就发现,仅仅在脚本开头调用 trap 是不够的。
有什么方法可以使用 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(variation 1)或没有(variation 2)。
在Ctrl+C之后,通常"trapped 2"首先被激活,很少"end 2"。这表明(与我最初的看法相反),处理信号时不涉及进程层次结构。正在运行的进程(复合语句、2 个进程替换、在 h 和 h2 中的子shell、sleep 进程、cat 进程)并行运行,并且在传递信号时恰好正在运行,将处理它。出于某种原因,这主要是 stderr 重定向的进程替换。我想cat 是主接收器,它没有安装信号处理程序,所以它就死了(这就是为什么我尝试添加 2 个cats,以便第二个可以保持子shell 运行)。
这就是重点,我没有真正的线索,会发生什么。 (我什至不知道,如果我做到这一点......)
我认为,信号将从cat 传播到它的包含进程,即进程替换 bash shell,它安装了信号处理程序,并打印"trapped 2"。
现在,我原以为故事会到此结束,一枚戒指被伊熙尔杜摧毁,佛罗多留在家里……但没有。不知何故,它冒了出来,并设法杀死了sleep。即使有 2 个cats,所以如果一个被销毁,子shell 仍然保持活动状态。我发现SIGPIPE 很可能是杀死睡眠的原因,因为没有捕获它,我看到的行为与我在此处发布的不同。但有趣的是,我似乎需要在每个位置trapSIGPIPE,而不仅仅是在睡眠子shell中,或者再次显示不同的行为。
我猜,SIGPIPE 信号到达sleep,杀死它,所以复合语句中只剩下一个echo,它执行,并且那个子shell 完成了。 stdout 重定向的进程替换也被杀死了,可能是另一个SIGPIPE 被杀死的复合语句/函数外壳?
更有趣的是,有时"trapped 1" 根本不显示。
奇怪的是我没有看到 50% "trapped 2" 和 50% "trapped 1"。
我可以做什么,我想要什么?
请记住,我的目标是有序关闭系统/服务/脚本。
1) 首先,如我所见,如果“业务流程”,这里由sleep/cat 表示,没有自己的信号处理,没有多少@ 987654358@可以让他们免于被杀。
2) 信号处理程序不是继承的,每个子shell 都必须有自己的陷阱系统。
3) 没有什么能像进程组那样以公共方式处理信号,无论信号碰巧碰到哪个进程都会做它的事情,并且在那里被杀死的进程的结果可能会在进程树中传播得更远。
不过,我不清楚,如果一个进程不能处理一个信号,它会把它扔到它的包含外壳吗?或者是另一个信号,传递了什么?有些东西肯定会通过,否则不会触发信号处理程序。
在/我的理想世界中,trap 将保护外壳内安装它的任何东西不接收信号,因此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
不过,我尝试用while : ; do sleep 0.1; done 替换sleep 63,结果如下:
测试设置 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,仍然不知道它来自哪里。
【问题讨论】:
-
@shellter 是的,为什么不呢? Ctrl-C 只是一个 SIGINT,它是可屏蔽的。请注意,bash 的行为取决于交互性、作业控制是否有效,bash 手册页特别提到了 SIGINT 在不同条件下的行为不同。不幸的是,我不是这方面的专家。我的问题是专门针对从脚本文件(不是来源,不是复制粘贴)运行脚本的情况。
-
是的,刚刚测试过,你可以捕获Ctrl-C。好的..我记得一位 Unix 老师向我们展示(30 年前;-/!)如何使用嵌套陷阱来捕捉用户反复按下 break 键。而且我想我已经在这里发布的至少一个代码块中看到了这一点,但这是一件罕见的事情。也许它被记录在某个地方并且可以帮助你(只是给你一些想法,因为 bash a-team 没有回应)。祝你好运!
-
谢谢!您最初的评论已经让我开始对信号进行更详细的研究,希望能找到一些有用的东西。
标签: linux bash shell process-substitution