5

I was trying to write a shell script that idly waits for a signal in the background. Since the script doesn't take user input I thought of using read to block the script indefinitely while waiting.

In bash the following code seems to work as expected, outputing "signal" every time it receives a SIGUSR1:

#!/bin/bash
trap "echo signal" SIGUSR1
read
$ ./test
signal
signal
...

However if I run with #!/bin/sh or BusyBox ash, sending SIGUSR1 also causes the program to terminate:

#!/bin/sh
trap "echo signal" SIGUSR1
read
$ ./test
signal
$

Shouldn't read only return after reading EOF or IFS from standard input? In this case that didn't happen, so what caused its return?

1 Answer 1

10

Dash and BusyBox comply with the POSIX specification by making the signal quit read immediately. Bash doesn't, unless invoked in POSIX mode. The section on signals states

When a signal for which a trap has been set is received while the shell is waiting for the completion of a utility executing a foreground command, the trap associated with that signal shall not be executed until after the foreground command has completed.

read is not a special built-in, so it is a “utility executing as a foreground command”, even if it happens to run inside the same process. This rules out the bash behavior of running the trap and continuing the execution of read.

The section on the execution environment clarifies how signals work when running a utility (once again, including non-special built-ins):

If the utility is a shell script, traps caught by the shell shall be set to the default values and traps ignored by the shell shall be set to be ignored by the utility; if the utility is not a shell script, the trap actions (default or ignore) shall be mapped into the appropriate signal handling actions for the utility

A built-in is not a shell script, but arguably it's still running as part of a shell that's executing a script, so the first clause could apply. But neither behaviors allow the utility to run the parent's trap. The only possible behaviors are for read to ignore the signal, block it, or let it return immediately. The description of read doesn't mention signals, so it isn't allowed to change the mask of a standard signal, which rules out ignoring. read could arguably block the signal until it finishes (programs may temporarily block signals for internal purposes) — but this would conflict with the expectation that read does not block a SIGINT while it's waiting for input (with Occam's razor saying that it wouldn't make sense to only have this behavior for SIGINT). So having read do nothing upon SIGUSR1 (which is how mksh behaves) might be technically compliant, but I don't find this to be reasonable behavior.

Dash and BusyBox aren't fully POSIX-compliant. Both (as of the versions in Ubuntu 22.04) set $? to 1 if read is interrupted by a signal, which contradicts the requirement on exit status.

The exit status of a command that terminated because it received a signal shall be reported as greater than 128.

(In practice that's 128 + signal value, except ATT ksh where it's 256 + signal value.)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .