2

I learned from https://unix.stackexchange.com/a/230568/674 thatping will exit with 0 after receiving SIGINT, which allows a bash script containing a ping command to continue running instead of exiting.

I have a script with similar behavior:

#!/bin/bash                                                                                                                                                                       

while true; do
    sudo -S sleep 4;
    echo $?
    sudo -k;
done

When I run it, I type Ctrl-C when it asks me for password, and the script doesn't exit, but continue running. The only difference is that sudo upon receiving SIGINT exits with 1 not 0. So I wonder why the bash script doesn't exit but continue running? Thanks.

$ ./test.sh 
[sudo] password for t: 
1
[sudo] password for t: 
1
[sudo] password for t: 
1
...
Tim
  • 101,790

2 Answers2

2

The test isn't a simple "did the command succeed" test. When a process exits with SIGINT, its exit status can be read by wait(2). By using WIFSIGNALED and WTERMSIG, bash can tell whether the child process (in this case, sudo) was killed directly by the signal or not.

Here's the result of that system call when hitting Ctrl+C in cat (according to strace):

wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGINT}], 0, NULL) = 9357

And here's the result when hitting Ctrl+C in sudo -S sleep 4:

wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 9479

Just for completeness, here's the result from Ctrl+C'ing ping localhost:

wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 9710
  • Thanks. Could you explain what the three lines of strace output mean? – Tim Nov 01 '18 at 00:52
  • The important bit is the section inside the square brackets. In the first case, it's telling the caller that the process died directly by the SIGINT signal. In the other cases, it's telling the caller that the process died by exiting, with statuses 1 and 0 respectively. – Joseph Sible-Reinstate Monica Nov 01 '18 at 00:53
  • 1
    this absolutely does not explain why the bash script does not terminate itself upon receiving a SIGINT, but continues running the loop (in fact, if the script will terminate if it is run with dash or zsh instead of bash or ksh). I remember a discussion that was detailing the differences between shells in this regard, but I suck at googling and the only thing I could find is this which is explaining it as if it were the obvious, expected behavior. –  Nov 01 '18 at 10:48
2

Apparently the sudo command handles INT signal so that it just exits unlike e.g. sleep. Thus Bash thinks that it does not have to kill itself in its default handler for SIGNIT:

bash is among a few shells that implement a wait and cooperative exit approach at handling SIGINT/SIGQUIT delivery. When interpreting a script, upon receiving a SIGINT, it doesn't exit straight away but instead waits for the currently running command to return and only exits (by killing itself with SIGINT) if that command was also killed by that SIGINT. The idea is that if your script calls vi for instance, and you press Ctrl+C within vi to cancel an action, that should not be considered as a request to abort the script.

You could override this behavior by defining a trap for SIGINT:

trap 'trap - INT; kill -s INT $$' INT

Reference: https://mywiki.wooledge.org/SignalTrap (Chapters 4 and 5)

jarno
  • 620