Rebelion
Rebelion

Reputation: 13

Bash Trap SIGINT Multiple Times Doesn't Work

I don't know what's wrong but this script doesn't work. It should be always goes back to menu everytime I press CTRL+C.

#!/bin/bash

func_info()
{
    clear
    echo "This is info page."
    read -p "Press CTRL+C to back to menu or press enter exit..."
    exit
}

func_menu()
{
    clear
    trap - SIGINT
    echo "This is menu page."
    read -p "Press enter to go to info page..."
    trap func_menu SIGINT
    func_info
}

func_menu

It works for the first CTRL+C but the second times it just doesn't works.

I'm new to this so please don't judge me ;)

Any helps appreciated :) Thanks.

EDIT:

Actually, I found this works

#!/bin/bash

func_info()
{
    clear
    echo "This is info page."
    read -p "Press CTRL+C to back to menu or press enter exit..."
    exit
}

func_menu()
{
    clear
    echo "This is menu page."
    read -p "Press enter to go to info page..."
    ( trap exit SIGINT; func_info )
    func_menu
}

func_menu

But is that OK?

Upvotes: 1

Views: 435

Answers (1)

dimo414
dimo414

Reputation: 48794

As a rule of thumb, you want to do as little as possible during signal handling. Especially if you're intending to recover from the signal instead of exiting, it is generally preferable to simply record that the signal was sent and then actually handle the signal in your "normal" code.

So instead of recursively calling functions (inside traps) in order to navigate, let's keep track of the current page and display it in a loop. Then, all the SIGINT handler has to do is update the current-page variable.

Here's a working demo of a three-page flow like your example (using select to navigate, but any approach would work if you don't like select):

pages=(info news contact)
page=

info() { echo "For info, see the news page"; }
news() { echo "$(date +%Y-%m-%d): No news today"; }
contact() { echo "We cannot be reached."; }

while true; do
  trap - SIGINT
  clear
  if [[ -n "$page" ]]; then
    trap 'page=; continue' SIGINT
    echo "This is the ${page} page"
    "$page" # invokes the function named $page
    ( read -p $'\nPress CTRL+C to go back to menu or press enter to exit...' )
    exit
  else
    echo "This is the menu page"
    echo "Select a page number and press enter to go there."
    select page in "${pages[@]}"; do
      if [[ -n "$page" ]]; then break; fi
    done
  fi
done
  • Clear the trap at the beginning of the loop, so that ^C exits the script from the menu page
  • When displaying a page, configure a trap that resets page on SIGINT/^C
  • read in a subshell
    • as you found, a subshell helps, because SIGINT/^C interrupts the subshell as a whole rather than read specifically
    • and only exit if the read succeeds

Give it a whirl! It works great on my system (Bash 4.3)

Upvotes: 1

Related Questions