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
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'
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
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
ctrl+c
out of asudo
password prompt. Just kept asking again & again. I put at the topdidSudo="$(sudo pwd)";
& nowctrl+c
stops the script. If I enter my password successfully, the rest of mysudo
commands work. – Reed Aug 28 '20 at 20:39