是什么决定了脚本的后台进程是否获得终端的 SIGINT 信号?

Chr*_*ski 4 bash terminal signals wait

#!/usr/bin/env bash

sleep 3 && echo '123' &
sleep 3 && echo '456' &
sleep 3 && echo '999' &
Run Code Online (Sandbox Code Playgroud)

如果我运行它,并SIGINT通过终端按 control-c 发送,它似乎仍然回显123...输出。我认为这是因为它以某种方式分离?

wait < <(jobs -p)但是,如果我在脚本末尾添加(等待所有后台作业完成),然后运行它并发送,SIGINT不会123...显示输出。

如何解释这种行为?是否以wait某种方式拦截信号并将其传递给后台进程?或者它与进程是否“连接”到终端的某种状态有关?

我发现了一个可能与此相关的问题,但我无法弄清楚它与上述行为有何关系:为什么在使用 wait() 函数时不忽略 SIGCHLD 信号?

Kam*_*ski 8

信号拦截?传播?不

\n

wait不拦截信号。外壳没有通过它。信号既不会被拦截,也不会被传播。运行三个“后台”命令的子 shell 要么直接获取信号,要么不获取信号。

\n

您可以使用以下脚本进行测试:

\n
#!/usr/bin/env bash\n\nprintf \'My PID is %s.\\n\' "$$"\nsleep 15 && echo \'123\' &\nsleep 15 && echo \'456\' &\nsleep 15 && echo \'999\' &\nwait < <(jobs -p)\n
Run Code Online (Sandbox Code Playgroud)\n

该脚本的行为类似于带有wait. 我的意思是你可以用+终止它三个“后台”作业。CtrlC

\n

再次运行它,不要点击Ctrl+ C。该脚本将告诉您它自己的 PID ( $$)。SIGINT现在,如果您从另一个终端 ( )发送kill -s INT <pid_here>,那么它将终止脚本,但不会终止三个作业。

\n

但是,如果您发送SIGINT到整个进程组 ( kill -s INT -- -<pid_here>),则脚本作业将获取它。当您按下Ctrl+C并且您的终端设置为在击键时发送中断信号(通常是,stty -a包括intr = ^C)时,也会发生同样的情况,整个组都会收到该信号。

\n
\n

谁收到信号?

\n

这是关于前台进程组的。shell 执行的任务之一是通知终端哪个进程组位于前台。

\n
\n

终端可以具有与其关联的前台进程组。该前台进程组在处理信号生成输入字符 [\xe2\x80\xa6] 方面发挥着特殊作用。

\n

支持作业控制的命令解释器进程可以通过将相关进程放置在单个进程组中并将该进程组与终端相关联,将终端分配给不同的作业或进程组。[\xe2\x80\xa6]

\n
\n

来源

\n

这就是您的案例中真正发生的情况:

\n
    \n
  1. 最初您处于交互式 shell 中。shell是自己进程组的领导者(进程组ID等于shell的PID)。终端将此进程组识别为前台进程组。
  2. \n
  3. 上述 shell 启用了作业控制。(一般来说,支持作业控制的 shell 在交互运行时默认启用它。)当您启动脚本(或基本上任何非内置脚本)时,shell 会使其成为另一个进程组的领导者。终端被告知新进程组现在应被视为前台进程组。交互式 shell 通过这种方式将自己置于后台。
  4. \n
  5. 从 shebang 我们知道它正在bash解释脚本。非交互式 Bash 启动时默认禁用作业控制。它运行的命令不会成为它们自己的进程组的领导者(除非命令本身更改其进程组;这是可能的,但在您的情况下不会发生)。这三个子shell和三个sleep进程与运行脚本属于同一个进程组。
  6. \n
  7. 当脚本终止时,终端会被告知交互式 shell 的进程组现在应该被视为前台进程组(再次)。如果脚本进程组中的任何进程仍在运行,它将不再位于前台进程组中。
  8. \n
\n

这解释了您观察到的行为:

\n
    \n
  • Ctrl如果当你点击+时脚本正在运行,C那么它和基本上它的所有后代都会收到SIGINT. 脚本等待是因为wait或因为某些不带&. 重要的是它的进程组仍然是前台进程组,并且(孙)子进程属于该组。

    \n
  • \n
  • Ctrl如果当您按+时脚本不再运行,C则其后代(如果有)将不会收到SIGINT,因为它们不属于当前的前台进程组。

    \n
  • \n
\n
\n

玩转作业控制

\n

请注意,您可以通过禁用或启用作业控制来更改此行为。

\n

如果您在交互式 shell 中禁用作业控制 ( set +m) 并运行脚本,则 shell 将运行该脚本,但不会使其成为进程组的领导者。脚本中没有作业控制。该脚本及其所有子脚本基本上都属于交互式 shell 的进程组。该组将始终是前台进程组。Ctrl+之后,C所有进程(包括交互式 shell)都将收到SIGINT,无论脚本是否仍在运行。

\n

set -m如果您在脚本本身中启用作业控制 ( ),则三个子 shell 将被放入各自的进程组中。在类似的情况下,没有的命令&将成为进程组领导者,并且终端将被告知新的前台进程组。但是你的命令带有&,它们将成为领导者,但前台进程组不会改变。Ctrl+后,无论脚本是否仍在运行,也无论交互式 shell 是否启用了作业控制,C它们都不会收到。SIGINT

\n
\n

笔记

\n
    \n
  • 分隔符或终止符的含义&通常被描述为“在后台运行”,但它并不等同于“不在前台进程组中运行”。运行的命令&可以保留在前台进程组中(例如,如果禁用作业控制)。不运行的命令&可以离开前台进程组。您可以确定的是&“异步运行”。

    \n
  • \n
  • 您可能会对交互式 shell 在运行命令时将自身置于后台这一事实感到惊讶。这确实发生了。在交互式 Bash 中运行这些命令:

    \n
    set -m\ntrap \'echo "Signal received."\' INT\nsleep 999\n
    Run Code Online (Sandbox Code Playgroud)\n

    Ctrl+C

    \n

    您将看到sleep被中断,但 shell 没有收到信号。这是因为 shell 放入sleep了一个单独的进程组,该进程组是击键时的前台进程组。shell 不在(当时的)前台进程组中,这意味着它在后台。

    \n

    现在更改set -mset +m并再次运行:

    \n
    set +m\ntrap \'echo "Signal received."\' INT\nsleep 999\n
    Run Code Online (Sandbox Code Playgroud)\n

    Ctrl+C

    \n

    禁用作业控制后,sleep将在 shell 的进程组中运行。该组将始终是前台进程组。您将看到一条来自 的消息trap

    \n
  • \n
\n