Bash Scripting – SIGINT Trap Ignored During ‘Select’ Loop

bashselectsignalstrap:

When i use 'trap' combined with select loop, namely when i try to hit CTRL+C to break out while options are displayed, it will just print ^C in terminal. If i remove 'trap' from script it will normally exit out, that is it will accept CTRL+C.

I've tested this on two different versions of bash (One shipped with CentOS and one shipped with Fedora), and i have an issue with the one from Fedora (4.4.23(1)-release). Bash version 4.2.46(2)-release that is shipped with CentOS seems to work fine. I've also tested this on local terminal and remote (via ssh). And problem is always on Fedora side.

I will post code to see what I'm talking about

This one doesn't work:

#!/bin/bash

trap exit SIGINT

select opt in One Two Three; do
        break
done

If i were to remove the entire 'trap exit SIGINT' line, it will work fine and accept CTRL+C without issues.

Any ideas how to fix or bypass this ?

Best Answer

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.

Related Question