I wrote a simple bash script with a loop for printing the date and ping to a remote machine:
#!/bin/bash
while true; do
# *** DATE: Thu Sep 17 10:17:50 CEST 2015 ***
echo -e "\n*** DATE:" `date` " ***";
echo "********************************************"
ping -c5 $1;
done
When I run it from a terminal I am not able to stop it with Ctrl+C.
It seems it sends the ^C to the terminal, but the script does not stop.
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
No matter how many times I press it or how fast I do it. I am not able to stop it.
Make the test and realize by yourself.
As a side solution, I am stopping it with Ctrl+Z, that stops it and then kill %1
.
What is exactly happening here with ^C?
Best Answer
What happens is that both
bash
andping
receive the SIGINT (bash
being not interactive, bothping
andbash
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).Above,
bash
,sh
andsleep
receive SIGINT when I press Ctrl-C, butsh
exits normally with a 0 exit code, sobash
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 whileping
is running,bash
notes that you've pressedCtrl-C
in its SIGINT handlers, but sinceping
exits normally,bash
does not exit.If you add a
sleep 1
in that loop and pressCtrl-C
whilesleep
is running, becausesleep
has no special handler on SIGINT, it will die and report tobash
that it died of a SIGINT, and in that casebash
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 thebash
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. Andksh93
exits whenever a command is killed by SIGINT.You get the same behaviour as
bash
above but also: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:
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, thatbash
script is also interrupted). Doing anexit 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 FreeBSDsh
, 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: