1

I need help understanding exactly how SIGINT is propagated between a shell script's main flow and a function call inside that script. For my script, I have a main menu which takes user input and calls a child function based on that input. The child function interacts with the user.

My goals are the following:

  • I need the child function to have access to function definitions and variables from the main script, so subshells aren't a great option
  • The child function should be in its own file because otherwise my main shell script would be too big and unwieldy
  • I want a ctrl+c inside the child function to return the user to the main menu
  • I want a ctrl+c inside the main menu to ideally print something, and if done again, exit. If it has to exit the first time that is acceptable

The behavior I see is that I can hit ctrl+c either inside the main menu or from inside the child function and it will work as expected the first time, but all subsequent ctrl+c signals are ignored.

I feel like my issue is very close to these:

Though in their case they are calling the child in a new process, and I'm calling a sourced function, which I don't think would have the same implications for fg/bg, would it?

For a minimal example, let's say I have a file called main.sh:

trap catchInt SIGINT
reallyQuit=0
function catchInt(){
    echo "caught sigint"
    if (( $reallyQuit > 0 ));then
        echo "really quitting."
        exit 1
    else
        let reallyQuit++
        echo "reallyquit is now $reallyQuit"
    fi
    menu
}
function menu(){
    read -ep $'Please choose a number and press enter.\n\t' input
    case $input in
        1)
            child
            menu
            ;;
        *)
            echo "Quitting"
            exit 0
            ;;
    esac
}
source child.sh
# I also source other scripts with utility functions and exported vars
menu

And if I have a file called child.sh in the same directory:

function child(){
        read -p "Please type something"
        # I also use utility functions and exported vars
}

Here is an example run of the above code in which I hit ctrl+c inside the menu, and then try again inside the child function:

bash main.sh
Please choose a number and press enter.
    caught sigint
reallyquit is now 1
Please choose a number and press enter.
    1
Please type something^C^C^C^C^C (I finally pressed enter)
Please choose a number and press enter.
(I tapped ctrl-c a few times here and finally pressed enter)
Quitting

Here is an example in which I first type 1, then ctrl-c:

bash main.sh
Please choose a number and press enter.
    1
Please type something^Ccaught sigint
reallyquit is now 1
Please choose a number and press enter.
(I tapped ctrl-c a few times here and finally pressed enter)
Quitting

How can I get the trap to respond every time I send the INT signal?

CGanote
  • 149
  • 1
  • 6

1 Answers1

1

I do not know for sure, but I think it's because you're still in the "trap handler" when menu is called for the second time. Because a sigint is still being handled, a second sigint isn't processed. If you would remove that call and wrap menu with while true; do ...; done, it does work:

#! /bin/bash

reallyQuit=0
function catchInt(){
    echo "caught sigint"
    if (( $reallyQuit > 0 ));then
       echo "really quitting."
        exit 1
    else
        let reallyQuit++
        echo "reallyquit is now $reallyQuit"
    fi
}

trap catchInt SIGINT

function menu(){
    read -ep $'Please choose a number and press enter.\n\t' input
    case $input in
        1)
            child
            menu
            ;;
        *)
            echo "Quitting"
            exit 0
            ;;
    esac
}
function child(){
        read -p "Please type something"
        # I also use utility functions and exported vars
}

# I also source other scripts with utility functions and exported vars
while true; do
        menu
done
exit 0

EDIT:
Why do you include the child.sh in main.sh? Usually one would create common functions and include it in the child script. This way you can share the functions in main.sh among child1.sh,... , childN.sh. If would add source main.sh into child.sh, the traps will work as well.

Bayou
  • 3,293
  • 1
  • 9
  • 22
  • I see what you are saying about still being inside the handler. Running the revised code, if I hit ctrl+c while inside the child function, however, it isn't exiting the child - it returns to the read -p and waits. If I put a print statement in the child function after read it will print. How do I get out of the function call or out of the read? – CGanote Aug 26 '19 at 18:00
  • I had to work around the read part - I put another echo statement in to tell the user to press a key. I called the child instead of sourcing it so that the trap would effectively stop the child process, so that worked. The hairy part about sourcing my functions is that it doesn't work if I call the script from another directory (file not found), so I had to us $0 to get the path right. Not sure if that's the way to handle all this, but at least the trap now works, all hacked together! – CGanote Aug 27 '19 at 01:32
  • I wasn't able to reply earlier, sorry for that. `read` also "hangs" the trap until user input, therefore it didn't work (but you already figured that out). About the `$0` part, when the working directory is important, I always use `cd "$(dirname "$0")"` before I `source` something just to be sure that my script always works. I think it's an appropriate solution. Good luck with the rest of your script! :-) – Bayou Aug 27 '19 at 11:36