要了解
如果 Bash 正在等待命令完成并接收到已设置陷阱的信号,则在命令完成之前不会执行陷阱。
当 Bash 通过 wait 内置函数等待异步命令时,接收到已设置陷阱的信号将导致内置 wait 立即返回,退出状态大于 128,然后立即执行陷阱。
根据 Bash 手册,我运行以下命令:
在我的两个示例中,SIGINT(使用 Ctrl-C 发送)立即终止前台作业(引用中的第一个案例)和后台作业(引用中的第二个案例),而不等待它们完成。
引用中的第一句话是否意味着如果 Bash 正在运行前台作业并接收 signal
SIGINT
,则 signal 的陷阱SIGINT
在设置后将被执行,直到命令完成?如果是,为什么在我的第一个示例中,ctrl-C
使前台作业在完成之前立即存在?$ sleep 10000 # a foreground job ^C $ sleep 10000 & # a background job [1] 21219 $ wait 21219 ^C $ echo $? 130
什么是“已设置陷阱的信号” 意思是,
arg
已通过 指定陷阱的信号trap arg sigspec
,或一个未被忽略的信号,或者
一个其陷阱不是默认陷阱的信号?
在第 1 部分的示例中,我没有为 SIGINT 设置陷阱,因此信号有其默认处理程序(它会中断任何执行循环)。是一个 具有默认处理程序的信号算不算已经设置了陷阱?
我设下陷阱
SIGINT
,但ctrl-C
会使以下命令在完成之前退出。那么这是否与我引用的第一句话相反?$ trap "echo You hit control-C!" INT $ bash -c 'sleep 10; echo "$?"' ^C $
在设置陷阱之前
SIGINT
,ctrl-C
也会使相同的命令在完成之前退出。那么这是否与我引用的第一句话相反?$ bash -c 'sleep 10; echo "$?"' ^C
你能举一些例子来解释一下引用中的两句话的意思吗?
谢谢。
答案1
“已设置陷阱的信号”是什么意思?
这是一个已定义处理程序的信号(使用trap 'handling code' SIG
),其中处理代码不为空,因为这会导致信号被忽略。
因此,具有默认配置的信号不是已设置陷阱的信号。您帖子中的一些引用也适用于具有默认配置的信号,尽管显然不是关于运行陷阱,因为没有为它们定义陷阱。
该手册讨论了信号传递到外壳,而不是您从该 shell 运行的命令。
1.
如果 Bash 正在等待命令完成并接收到已设置陷阱的信号,则在命令完成之前不会执行陷阱。
(1)
为什么在我的第一个示例中,ctrl-C 会使前台作业在完成之前立即退出
如果您sleep 10
在交互式 shell 的提示符下运行,shell 会将该作业放入前景(通过ioctl()
tty 设备上的一个告诉终端线路规则哪个进程组是前台进程组),所以只会sleep
收到 SIGINT ^C
,而交互式 shell 则不会,因此测试该行为没有用。
父 shell,因为它是交互式的,不会收到 SIGINT,因为它的进程不在前台进程组中。
每个命令都可以随意处理信号。
sleep
不会对 SIGINT 执行任何特殊操作,因此将获得默认配置(终止),除非在启动时忽略 SIGINT。
(2)如果您sleep 10
在非交互式 shell 中运行,
bash -c 'sleep 10; echo "$?"'
当您按 Ctrl-C 时,非交互式bash
shell 和sleep
都会收到 SIGINT。
如果bash
立即退出,sleep
如果碰巧忽略或处理 SIGINT 信号,则可能会使命令在后台无人值守地运行。所以与其,
bash
像大多数其他贝壳一样,块等待命令时接收信号(至少是一些信号)。- 命令退出后将恢复传送(执行陷阱)。这也避免了陷阱中的命令与其他命令同时运行。
在上面的例子中,sleep
将在 SIGINT 时死亡,所以bash
不需要很长时间来处理它自己的 SIGINT(这里死亡,因为我没有添加trap
on SIGINT)。
(3) 当您在运行非交互式 shell 时按 Ctrl+C:
bash -c 'sh -c "trap \"\" INT; sleep 3"; echo "$?"'
(没有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"'
bash
正在等待sh
(以及sleep
) SIGINT 被忽略(因为trap "" INT
),所以 SIGINT 不会杀死sleep
也sh
。bash
不忽略 SIGINT,但其处理被推迟到sh
返回。您看到的Ouch
显示不是在 上Ctrl+C,而是在sleep
和sh
正常终止之后。
请注意,该trap
命令为其运行的同一 shell 设置了一个信号陷阱。因此,当trap
命令在非交互式 shell 之外和父 shell 中执行时,
$ trap "echo You hit control-C!" INT $ bash -c 'sleep 10; echo "$?"' ^C $
非交互式bash
, 和sleep
命令不会trap
从父 shell 继承它。执行不同的命令时信号处理程序会丢失(execve()
擦除进程的整个地址空间,包括处理程序的代码)。一旦execve()
,定义了处理程序的信号恢复为默认配置,那些被忽略的信号仍然被忽略。除此之外,在大多数 shell 中,trap
s 也会在子 shell 中重置。
2.
当 Bash 通过 wait 内置函数等待异步命令时,接收到已设置陷阱的信号将导致内置 wait 立即返回,退出状态大于 128,然后立即执行陷阱。
wait
明确使用时, wait
会被任何设置了陷阱的信号中断(显然还有那些完全杀死 shell 的信号)。
这使得很难当存在捕获信号时可靠地获取命令的退出状态:
$ bash -c 'trap "echo Ouch" INT; sh -c "trap \"\" INT; sleep 10" & wait "$!"; echo "$?"'
^COuch
130
在这种情况下,sleep
并sh
没有被 SIGINT 杀死(因为他们忽略了它)。仍然wait
返回130
退出状态,因为在等待时收到了信号 (SIGINT) sh
。您需要重复wait "$!"
直到sh
真正终止才能获取sh
退出状态。