gpt4 book ai didi

linux - bash陷阱和过程替换

转载 作者:行者123 更新时间:2023-12-03 02:07:47 27 4
gpt4 key购买 nike

更新

我为发布的答案使用了更好的测试用例。我在这里添加了更新的测试用例,以防有人想进一步试验:

#!/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只猫,没有睡眠子 shell ):
    #!/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"。这表明(与我最初的想法相反),处理信号时不涉及过程层次。正在运行的进程(compound语句,2个进程的替换,在h和h2中的子 shell , sleep进程, cat进程)并行运行,并且在信号传递时碰巧正在运行的任何一个都将对其进行处理。由于某些原因,主要是stderr重定向的进程替换。我以为 cat是主要的接收者,没有安装信号处理程序,所以它就死了(这就是为什么我尝试添加2个 cat,以便第二个可以保持子 shell 运行)。

    在这一点上,我没有真正的头绪,会发生什么。 (我什至不知道,到现在为止我是否正确...)

    我认为,信号将从 cat传播到其包含的进程,即安装了信号处理程序的进程替换bash shell,并打印 "trapped 2"

    现在,我以为,故事将在这里结束,一个戒指被伊西尔杜尔(Isildur)摧毁,佛罗多(Frodo)呆在家里……但没有。它以某种方式冒泡,并设法杀死了 sleep。即使有2个 cat,所以即使其中一个被销毁,该子 shell 也会保持 Activity 状态。我发现 SIGPIPE最有可能杀死睡眠,因为没有捕获到该错误,我看到的行为与我在此处发布的有所不同。但有趣的是,似乎我需要在每个位置 trap SIGPIPE而不是仅在sleep子 shell 中,否则,它表现出不同的行为。

    我猜想, SIGPIPE信号到达 sleep,将其杀死,因此复合语句中只剩下一个 echo,它会执行并完成子 shell 。 stdout重定向的进程替换也被杀死,可能是由被杀死的复合语句/函数shell通过另一个 SIGPIPE杀死的?

    更有趣的是,有时根本没有显示 "trapped 1"

    奇怪的是,我没有看到50% "trapped 2"和50% "trapped 1"

    我可以做什么,我想要什么?

    请记住,我的目标是有序关闭系统/服务/脚本。

    1)首先,正如我所看到的,如果此处由 sleep/ cat表示的“业务流程”没有自己的信号处理,则没有多少 trap可以使它们免于被杀死。

    2)信号处理程序不是继承的,每个子shell必须具有自己的陷阱系统。

    3)没有什么像进程组那样可以以公共(public)方式处理信号了,无论信号碰到哪个进程都会发挥作用,并且在那里杀死的进程的结果可能会在进程树中进一步传播。

    但是,对于我来说还不清楚,如果一个进程无法处理信号,它将把它扔到它的包含 shell 中吗?还是另一个信号,传递了什么?一定会通过,否则将不会触发信号处理程序。

    在一个理想的世界中, trap可以保护安装在其内的 shell 中的任何东西都不接收信号,因此 sleep -s, cat -s将被指定的清理功能关闭:杀死 sleep,其余将记录其最后几行,然后跟随-而不是:清除所有日志记录,只有在此之后,主进程才最终被杀死...

    我错过了一些琐碎的事情吗?设置-o魔法?只是继续添加更多陷阱,直到它突然起作用??

    问题:

    信号在 Ctrl+C之后如何真正传播?
    SEGFAULT来自哪里?

    最重要的:

    从日志记录开始,我是否可以保护此结构免遭信号干扰?还是应该避免进程替换,而提出另一种类型的输出过滤/日志记录?

    经过测试:

    GNU bash版本4.4.12(1)-发行版(x86_64-pc-linux-gnu)

    进一步说明:

    在完成测试之后,我发现了这些QA,我认为这可能与我的情况有关,但我不知道确切地我该如何利用它们:

    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仍然不知道它来自何处。

    最佳答案

    经过无数次实验,我得出的结论是,不可能做自己想做的事,但我仍然不了解每个细节。

    我发布了我的发现,但是一会儿不接受我的回答,以防万一-希望-有人对正在发生的事情有更好的了解。

    看来,我错了很多事...

    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进程组中的每个进程传递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在其自己的 session 中开始,该 session 将具有一个全新的进程组,该进程组将仅包含 cmd,因此它很可能用于分隔记录器和记录器。我没有考虑过,也没有尝试过。

    Running a process in the background with input/output redirection

    其他引用:

    Send command to a background process

    Signals

    Process group

    Job control (Unix)

    Why Bash is like that: Signal propagation

    How to propagate SIGTERM to a child process in a Bash script

    结论

    在设置中:
    {
    cmd
    } \
    2> >(logger) \
    1> >(logger)

    我没有找到在进程组级别将 cmdlogger分开的好方法。将 logger s设置为背景会使它们无法接收输出,而是可能会通过 SIGSTOP立即终止。

    一种解决方案是使用命名管道,这将允许更大的控制权,并有可能分离已记录和记录程序。但是,我最初决定采用bash提供的进程替换,以避免手动编码管道所带来的复杂性。

    最后,我选择的方法是简单地使整个过程树( cmd + logger)成为背景,并让另一个层次处理这些信号。
    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

    关于linux - bash陷阱和过程替换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47012675/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com