8

The following Bash loop stops if I interrupt it with ^C:

(while true; do sleep 5; done)

The following, however, does not:

(while true; do aplay test.wav; done)

The difference, from what I can tell, is that aplay catches and handles SIGINT, whereas sleep does not.

Why is this? Does POSIX specify this behavior somewhere (I notice that Dash does the same thing), and if so, why? I cannot quite understand why this behavior is desirable. In fact, how can Bash even tell the difference? I must admit I'm not aware of any mechanism (other than ptrace or /proc/ hacking, at least) by which one process can tell how another one handles signals.

Also, is there a way to counteract it? I notice that not even a trap 'exit 0' SIGINT before the loop helps.

(EDIT: Running the loops in a subshell is important, because otherwise the parent shell does not receive the SIGINT.)

Dolda2000
  • 315

3 Answers3

9

The problem is well explained here, as WCE wait and cooperative exit and I encourage you to read it. Basically, your signal is received by all foreground processes, ie the shell and the program, sleep or aplay. The program exits with return code 130 if it does not handle the signal. The shell does a wait for the child to end, and sees this, and the fact that it got the signal too, so exits.

When the program captures the signal, it often just exits with code 1 (as with aplay). When the shell waits for the child, it sees that it did not end due to the signal and so has to assume the signal was a normal aspect of the program's working, so the shell carries on as normal.

For your example, the best way to handle aplay is to check its return code for non-zero and stop:

(while aplay test.wav; do :; done)

The above-mentioned article goes on to explain that a well-behaved program that wants to trap sigint to do some cleanup, should then disable its handler, and re-kill itself in order to get the correct exit flags set.

meuh
  • 51,383
  • That certainly seems correct, though I find the behavior somewhat puzzling (I don't quite understand why a proper subshell wouldn't just want to quit from the signal). I'll have to investigate further. – Dolda2000 Jun 04 '16 at 00:11
  • @Dolda2000: think of what you'd want this script to do: "#!/bin/bash\n emacs foo.c \n rsync -av foo.c user@backup_server:/src/". If all scripts died immediately when their children got sigints, this kind of script would not work (your code would never sync up remotely). The script needs to know if its child died because of the sigint (in which case it must stop further execution) or just exited - in which case it must continue. Arguably, 'aplay' has a bug - it doesn't communicate the SIGINT as a cause of death - and just exits. – ttsiodras Jun 11 '16 at 19:20
  • @ttsiodras: "your code would never sync up remotely" -- How so? Are you implying that Emacs only exits because of SIGINTs? That's unlike the Emacs I know, at least. – Dolda2000 Jun 12 '16 at 01:51
  • @Dolda2000: I mean that the rsync that is supposed to execute after emacs finishes, would not be executed - because in your scenario "the subshell would quit after the signal". – ttsiodras Jun 13 '16 at 05:51
  • @ttsiodras: Why would the subshell receive a signal at all during normal operation? – Dolda2000 Jun 15 '16 at 03:50
  • @Dolda2000 It is explained in the link I gave: Emacs remaps the key that sends SIGINT from C-c to C-g and catches SIGINT. Typing control-g can happen often and naturally in emacs to interrupt a long operation. (this assumes emacs is running in the tty, not creating a new window). – meuh Jun 15 '16 at 05:33
  • 1
    The program does not actually exit with code 130. https://unix.stackexchange.com/questions/386836/ – JdeBP Feb 15 '19 at 18:49
0

The script won't receive the signal until the child process completes. But if you run it in the background and then wait on it, the parent will get the signal:

(while true; do aplay test.wav; done) &
wait $!
-3

you could try this, i had this issue a few times and idk why this worked by it did.

$(sigint)

this is to dislay the result like a var is bash, but to do so it has to run it and some times works.