nep*_*tom 47 bash terminal signals
我编写了一个带有循环的简单 bash 脚本,用于打印日期和 ping 到远程机器:
#!/bin/bash
while true; do
# *** DATE: Thu Sep 17 10:17:50 CEST 2015 ***
echo -e "\n*** DATE:" `date` " ***";
echo "********************************************"
ping -c5 $1;
done
Run Code Online (Sandbox Code Playgroud)
当我从终端运行它时,我无法使用Ctrl+C. 它似乎将 发送^C到终端,但脚本并没有停止。
MacAir:~ tomas$ ping-tester.bash www.google.com
*** DATE: Thu Sep 17 23:58:42 CEST 2015 ***
********************************************
PING www.google.com (216.58.211.228): 56 data bytes
64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
^C <= That is Ctrl+C press
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms
*** DATE: Thu Sep 17 23:58:48 CEST 2015 ***
********************************************
PING www.google.com (216.58.211.196): 56 data bytes
64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms
Run Code Online (Sandbox Code Playgroud)
无论我按多少次或按多快。我无法阻止它。
进行测试并自己实现。
作为一个辅助解决方案,我用Ctrl+Z停止它,然后停止它kill %1
。
这里到底发生了^C什么?
Sté*_*las 33
What happens is that both bash
and ping
receive the SIGINT (bash
being not interactive, both ping
and bash
run in the same process group which has been created and set as the terminal's foreground process group by the interactive shell you ran that script from).
However, bash
handles that SIGINT asynchronously, only after the currently running command has exited. bash
only exits upon receiving that SIGINT if the currently running command dies of a SIGINT (i.e. its exit status indicates that it has been killed by SIGINT).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Run Code Online (Sandbox Code Playgroud)
Above, bash
, sh
and sleep
receive SIGINT when I press Ctrl-C, but sh
exits normally with a 0 exit code, so bash
ignores the SIGINT, which is why we see "here".
ping
, at least the one from iputils, behaves like that. When interrupted, it prints statistics and exits with a 0 or 1 exit status depending on whether or not its pings were replied. So, when you press Ctrl-C while ping
is running, bash
notes that you've pressed Ctrl-C
in its SIGINT handlers, but since ping
exits normally, bash
does not exit.
If you add a sleep 1
in that loop and press Ctrl-C
while sleep
is running, because sleep
has no special handler on SIGINT, it will die and report to bash
that it died of a SIGINT, and in that case bash
will exit (it will actually kill itself with SIGINT so as to report the interruption to its parent).
As to why bash
behaves like that, I'm not sure and I note the behaviour is not always deterministic. I've just asked the question on the bash
development mailing list (Update: @Jilles has now nailed down the reason in his answer).
The only other shell I found that behave similarly is ksh93 (Update, as mentioned by @Jilles, so does FreeBSD sh
). There, SIGINT seems to be plainly ignored. And ksh93
exits whenever a command is killed by SIGINT.
You get the same behaviour as bash
above but also:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Run Code Online (Sandbox Code Playgroud)
Doesn't output "test". That is, it exits (by killing itself with SIGINT there) if the command it was waiting for dies of SIGINT, even if it, itself didn't receive that SIGINT.
A work around would be to do add a:
trap 'exit 130' INT
Run Code Online (Sandbox Code Playgroud)
At the top of the script to force bash
to exit upon receiving a SIGINT (note that in any case, SIGINT won't be processed synchronously, only after the currently running command has exited).
Ideally, we'd want to report to our parent that we died of a SIGINT (so that if it's another bash
script for instance, that bash
script is also interrupted). Doing an exit 130
is not the same as dying of SIGINT (though some shells will set $?
to same value for both cases), however it's often used to report a death by SIGINT (on systems where SIGINT is 2 which is most).
However for bash
, ksh93
or FreeBSD sh
, that doesn't work. That 130 exit status is not considered as a death by SIGINT and a parent script would not abort there.
So, a possibly better alternative would be to kill ourself with SIGINT upon receiving SIGINT:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
Run Code Online (Sandbox Code Playgroud)
小智 16
解释是 bash 根据http://www.cons.org/cracauer/sigint.html为 SIGINT 和 SIGQUIT 实现了 WCE(等待和合作退出)。这意味着如果 bash 在等待进程退出时收到 SIGINT 或 SIGQUIT,它将等到进程退出,如果进程在该信号上退出,它将自行退出。这可确保在其用户界面中使用 SIGINT 或 SIGQUIT 的程序将按预期工作(如果该信号未导致程序终止,则脚本将正常继续)。
捕获 SIGINT 或 SIGQUIT 但随后由于它而终止但使用正常的 exit() 而不是通过向自己重新发送信号的程序出现了一个缺点。可能无法中断调用此类程序的脚本。我认为真正的修复存在于诸如 ping 和 ping6 之类的程序中。
ksh93 和 FreeBSD 的 /bin/sh 实现了类似的行为,但大多数其他 shell 没有实现。
终端注意到 control-c 并向INT
前台进程组发送信号,这里包括 shell,因为ping
还没有创建新的前台进程组。这很容易通过陷印来验证INT
。
#! /bin/bash
trap 'echo oh, I am slain; exit' INT
while true; do
ping -c5 127.0.0.1
done
Run Code Online (Sandbox Code Playgroud)
如果正在运行的命令创建了一个新的前台进程组,则 control-c 将转到该进程组,而不是 shell。在这种情况下,shell 将需要检查退出代码,因为终端不会发出信号。
(INT
处理在弹可惊人复杂,顺便说一下,作为壳有时需要忽略该信号,并且有时不源潜水如果好奇,或思考:tail -f /etc/passwd; echo foo
)
正如您猜测的那样,这是由于 SIGINT 被发送到从属进程,并且在该进程退出后 shell 继续运行。
为了更好地处理这个问题,您可以检查正在运行的命令的退出状态。Unix 返回码对进程退出的方法(系统调用或信号)以及传递给什么值exit()
或终止进程的信号进行编码。这一切都相当复杂,但最快的使用方法是知道由信号终止的进程将具有非零返回码。因此,如果您检查脚本中的返回代码,您可以在子进程终止时退出自己,从而消除不必要的sleep
调用等不优雅的需要。在整个脚本中执行此操作的一种快速方法是使用set -e
,尽管它可能需要对退出状态为预期非零的命令进行一些调整。
好吧,我尝试将 a 添加sleep 1
到 bash 脚本中,然后砰!
现在我可以用两个来阻止它Ctrl+C。
当按下 时Ctrl+C,一个SIGINT
信号被发送到当前执行的进程,该命令在循环内运行。然后,子 shell进程继续执行循环中的下一个命令,从而启动另一个进程。为了能够停止脚本,需要发送两个SIGINT
信号,一个用于中断当前正在执行的命令,另一个用于中断子 shell进程。
在没有调用的脚本中sleep
,按得Ctrl+C非常快并且多次似乎不起作用,并且不可能退出循环。我的猜测是,按两次不够快,不足以使其恰好在当前执行进程中断和下一个进程开始之间的正确时刻。每次Ctrl+C按下都会将 a 发送SIGINT
到循环内执行的进程,但不会发送到subshell。
在脚本 with 中sleep 1
,这个调用将暂停执行一秒钟,当被第一个Ctrl+C(first SIGINT
)中断时,子shell将花费更多时间来执行下一个命令。所以现在,第二个Ctrl+C(第二个SIGINT
)将进入子shell,脚本执行将结束。
归档时间: |
|
查看次数: |
41260 次 |
最近记录: |