在终端中键入 ctrl-c 时,为什么前台作业在完成之前不终止?

Tim*_*Tim 0 bash signals

了解

如果 Bash 正在等待命令完成并接收到设置了陷阱的信号,则在命令完成之前不会执行陷阱。

当 Bash 通过 wait 内置函数等待异步命令时,接收到设置了陷阱的信号将导致 wait 内置函数立即返回,退出状态大于 128,在此之后陷阱将立即执行。

从 Bash 手册中,我运行以下命令:

  1. 在我的两个示例中,SIGINT(使用 Ctrl-C 发送)立即终止前台作业(引用中的第一种情况)和后台作业(引用中的第二种情况),无需等待它们完成。

    引用中的第一句话是否意味着如果 Bash 正在运行前台作业并接收到信号SIGINT,则信号的陷阱 SIGINT在设置后将一直执行直到命令完成?如果是,为什么在我的第一个示例中,ctrl-C使前台作业在完成之前立即存在?

    $ sleep 10000  # a foreground job
    ^C
    
    
    $ sleep 10000 & # a background job
    [1] 21219
    $ wait 21219
    ^C
    $ echo $?
    130
    
    Run Code Online (Sandbox Code Playgroud)
  2. “已设置陷阱的信号”是什么 意思,

    • arg已通过 指定陷阱的信号trap arg sigspec,或

    • 一个不被忽略的信号,或

    • 一个信号的陷阱不是默认的?

    在我的第 1 部分的示例中,我没有为 SIGINT 设置陷阱,因此信号有其默认处理程序(它会跳出任何执行循环)。是否认为具有默认处理程序信号已设置陷阱?

  3. 我为 设置了一个陷阱SIGINT,但ctrl-C会在完成之前退出以下命令。那么它是否与我引述中的第一句话相反?

    $ trap "echo You hit control-C!" INT
    $ bash -c 'sleep 10; echo "$?"'
    ^C
    $
    
    Run Code Online (Sandbox Code Playgroud)

    在为 设置陷阱之前SIGINTctrl-C也会在完成之前使相同的命令退出。那么它是否与我引述中的第一句话相反?

    $ bash -c 'sleep 10; echo "$?"'
    ^C
    
    Run Code Online (Sandbox Code Playgroud)
  4. 你能举一些例子来解释引文中的两句话是什么意思吗?

谢谢。

Sté*_*las 5

“已设置陷阱的信号”是什么意思?

这是一个已为其定义处理程序的信号(使用trap 'handling code' SIG),其中处理代码不为空,因为这会导致信号被忽略。

因此,具有默认处置的信号不是已为其设置陷阱的信号。您帖子中的一些引用也适用于具有默认处置的信号,尽管显然不是关于运行 trap的部分,因为尚未为它们定义陷阱。


手册讨论信号传递到 shell,而不是你从那个 shell 运行的命令。

1.

如果 Bash 正在等待命令完成并接收到设置了陷阱的信号,则在命令完成之前不会执行陷阱。

(1)

为什么在我的第一个示例中,ctrl-C 会在前台作业完成之前立即退出

如果您sleep 10在交互式 shell 的提示下运行,shell 将把该作业放在前台(通过ioctl()tty 设备上的一个告诉终端线路规则哪个进程组是前台),所以只会sleep得到一个 SIGINT^C并且交互式外壳不会,因此测试该行为没有用。

  • 父 shell,因为它是交互式的,所以不会收到 SIGINT,因为它的进程不在前台进程组中。

  • 每个命令都可以随意处理信号。sleep不会对 SIGINT 做任何特别的事情,因此除非在启动时忽略 SIGINT,否则将获得默认处置(终止)。

(2)如果您sleep 10在非交互式 shell 中运行

bash -c 'sleep 10; echo "$?"'
Run Code Online (Sandbox Code Playgroud)

当您按下 Ctrl-C 时,非交互式bashshell 和sleepSIGINT 都会收到。

如果立即bash退出,sleep如果它碰巧忽略或处理 SIGINT 信号,它可能会使命令在后台无人看管。所以与其,

  • bash像大多数其他 shell 一样,在等待命令时阻止接收信号(至少是某些信号)。
  • 命令退出后(执行陷阱时)恢复传送。这也避免了陷阱中的命令与其他命令同时运行。

在上面的那个例子中,sleep将在 SIGINT 上消亡,所以bash不必等待很长时间来处理它自己的 SIGINT(这里死了,因为我没有trap在 SIGINT 上添加一个)。

(3) 当您在运行非交互式 shell 时按 Ctrl+C:

bash -c 'sh -c "trap \"\" INT; sleep 3"; echo "$?"'
Run Code Online (Sandbox Code Playgroud)

trap在 SIGINT上没有)bash不会被 SIGINT 杀死。bash,就像其他一些 shell 一样特别对待 SIGINT 和 SIGQUIT。它们实现了https://www.cons.org/cracauer/sigint.html 中描述的等待和合作退出行为(并且已知会引起一些烦恼,例如调用 SIGINT 处理命令的脚本不能被中断^C

(4)要正确测试,你应该运行一个非交互式bash的是具有SIGINT设下的陷阱,并调用一个不马上在SIGINT死命令,如:

bash -c 'trap "echo Ouch" INT; sh -c "trap \"\" INT; sleep 3"'
Run Code Online (Sandbox Code Playgroud)

bash正在等待sh(以及sleep)忽略 SIGINT(因为trap "" INT),因此 SIGINT 不会杀死sleepsh. bash不会忽略 SIGINT,但它的处理被推迟到sh返回。您看到Ouch正在显示,而不是在 上Ctrl+C,而是在sleepsh正常终止之后。

请注意,该trap命令为其运行的同一个 shell 设置了一个信号陷阱。所以当trap命令在非交互式 shell 和父 shell 之外执行时,

$ trap "echo You hit control-C!" INT
$ bash -c 'sleep 10; echo "$?"'
^C
$
Run Code Online (Sandbox Code Playgroud)

noninteractivebashsleep命令不会trap从父 shell继承它。信号处理程序在执行不同的命令时丢失(execve()擦除进程的整个地址空间,包括处理程序的代码)。在 上execve(),定义了处理程序的信号恢复为默认处理,那些被忽略的信号仍然被忽略。除此之外,在大多数 shell 中,traps 也在子 shell 中被重置。

2.

当 Bash 通过 wait 内置函数等待异步命令时,接收到设置了陷阱的信号将导致 wait 内置函数立即返回,退出状态大于 128,在此之后陷阱将立即执行。

wait显式使用时wait 会被任何设置了陷阱的信号(显然还有那些完全杀死外壳的信号)中断。

这使得在有陷阱信号时很难可靠地获得命令的退出状态

$ bash -c 'trap "echo Ouch" INT; sh -c "trap \"\" INT; sleep 10" & wait "$!"; echo "$?"'
^COuch
130
Run Code Online (Sandbox Code Playgroud)

在那种情况下,sleepsh没有被 SIGINT 杀死(因为他们忽略了它)。仍然wait返回130退出状态,因为在等待 时收到信号 (SIGINT) sh。您需要重复wait "$!"直到sh真正终止才能获得sh退出状态。