3

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 ?

3
  • Is break specified to leave select command?
    – Cyrus
    Apr 19, 2019 at 20:53
  • This sounds like the while true ; do cat ; done example in the bug report sr #108721: SIGINT trap not executing properly when commands interrupted; strange interaction with loops. which I cannot reproduce on bash 4.4.12(1)-release. Also your example works fine here (trap is executed).
    – Freddy
    Apr 19, 2019 at 21:21
  • @Cyrus yeah, just wanted to make code look smaller than it originally is. I kept on reducing it to figure out where the problem lies and i figured out it's nothing about my scripting methods, but it relates to 'trap'. So i didn't want to put a longer version of code here since it doesn't seem to matter. Original code will check against every response, filter out the correct one and then break out. Freddy, it does sound like a bug to me too. So I'll just check for updates to bash then :) Apr 20, 2019 at 11:18

2 Answers 2

6

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.

2
  • This works fine when i test it ! Thanks ! Later on i will check against the actual code that I've written to see if there is something else wrong if i turn on the posix. Apr 20, 2019 at 11:19
  • Yup! Definitely works! Thanks :) Apr 30, 2019 at 18:08
2

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.

5
  • That's not convincing; if select is a command, then read is a command too, but ^C will break out of a read but not out of a select. See also stackoverflow.com/a/53424823/10306503
    – user313992
    Apr 20, 2019 at 1:02
  • @mosvy Your answer on SO seems to say that SIGINT won't break out of read (as in cancel) when a trap handler is installed for that signal, which is what I also have observed. The difference between select and read seems to be that the trap handler is executed asynchronously with read while it's executed after input to select.
    – Kusalananda
    Apr 20, 2019 at 6:54
  • It will break out if it calls exit ;-)
    – user313992
    Apr 20, 2019 at 6:55
  • @mosvy Yes, if the trap handler calls exit, obviously. What I meant was that the act of catching the signal does not cancel the read.
    – Kusalananda
    Apr 20, 2019 at 7:01
  • @mosvy I wonder if the difference is that read is a simple command while select is a compound command? Hmm... too much handwaving, I'll just ask on help-bash.
    – Kusalananda
    Apr 20, 2019 at 7:10

You must log in to answer this question.

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