Bash ignoring SIGINT trap when 'select' loop is running
Any ideas how to fix or bypass this ?
You can bypass it by turning on the posix mode, either with the --posix
option, or temporarily with set -o posix
:
set -o posix
select opt in foo bar baz; do
echo "opt=$opt"
done
set +o posix
For an explanation for this behavior, you can look at the zread()
function, which is used by the read
builtin (which is also called internally by bash in select
):
while ((r = read (fd, buf, len)) < 0 && errno == EINTR)
/* XXX - bash-5.0 */
/* We check executing_builtin and run traps here for backwards compatibility */
if (executing_builtin)
check_signals_and_traps (); /* XXX - should it be check_signals()? */
else
check_signals ();
For some special reason, the executing_builtin
is only set when the read
builtin is called explicitly, not when it's called by select
. This very much looks like a bug, not something deliberate.
When running in posix mode, a signal will cancel the read
builtin. In that case, zreadintr()
is called, which unlike zread()
, is not re-calling the interrupted read(2)
syscall after running the traps. See builtins/read.def
:
if (unbuffered_read == 2)
retval = posixly_correct ? zreadintr (fd, &c, 1) : zreadn (fd, &c, nchars - nr);
else if (unbuffered_read)
retval = posixly_correct ? zreadintr (fd, &c, 1) : zread (fd, &c, 1);
else
retval = posixly_correct ? zreadcintr (fd, &c) : zreadc (fd, &c);
More details about bash's "restarting" read
builtin here.
The relevant section from the bash
manual is (I believe; at least this is what it behaves like) this:
If
bash
is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.
So your trap handler will not be called until the body of the select
loop executes, because bash
is waiting for the command to complete. Once the input has been received by select
, the trap handler will execute.
The following modified script illustrates that better:
#!/bin/bash
trap 'echo INT;exit' SIGINT
select opt in One Two Three; do
printf 'Got %s (%s)\n' "$REPLY" "$opt"
done
Running it (with bash
5.0.3), selecting 1
, pressing Ctrl+C then Enter, and then selecting 3
.
$ bash script.sh
1) One
2) Two
3) Three
#? 1
Got 1 (One)
#? ^C
1) One
2) Two
3) Three
#? 3
INT
The trap handler is executed when the current input (3
) has been accepted and the before the body of the select
loop would have been executed.
The trap handler is not executed when I press Enter after Ctrl+C because pressing Enter at the select
prompt just re-displays the menu.