1

I have created a bash trap that traps CTRL + C and calls a function ctrl_c. This function just displays a message and starts a counter then returns to the main function.

The trap works fine the first time of running but if tried a second time it displays C^ although it does disable CTRL + C it doesn't call the function again.

Is there a way to reset the trap to run like the first instance.

Thanks in advance.

Code;

function ctrl_c() {
    clear
    echo "** Trapped CTRL-C"
    echo -n "Press [ Enter ] to continue."
    read
    for i in $(seq 1 5);
    do
            let timer="5 - $i + 1"
            clear
            echo "Returning to main menu in.. $timer"
            sleep 1
    done
    main
}

trap ctrl_c INT
Jolta
  • 2,620
  • 1
  • 29
  • 42
Seatter
  • 408
  • 1
  • 9
  • 19
  • 1
    Please provide the rest of your code/a minimal working example, your snippet does not contain a function called `main`. – Adrian Frühwirth Feb 26 '15 at 14:08
  • main is just a menu where the user can select option 1, 2, 3, 4. It's the trap that fails to call the function ctrl_c on the second time of running. – Seatter Feb 26 '15 at 14:15
  • 1
    You are running main from the trapped ctrl_c, so you are still inside the function and cannot trap ctrl_c when it is already trapped.To reset a trap though you can use `trap - INT` –  Feb 26 '15 at 14:18
  • How would a go about running main after trapping it? I have tried using trap - INT at the end of the function ctrl_c. – Seatter Feb 26 '15 at 14:22

3 Answers3

2

It's not a good idea to pass the control of the program to the signal handler. The signal handler is supposed to finish as fast as possible and return back. So you could enable a flag variable at your handler like the following:

function ctrl_c() {
    flag=1
}

function trap_menu() {
    clear
    for i in $(seq 1 5);
    do
        let timer="5 - $i + 1"
        clear
        echo "Returning to main menu in.. $timer"
        sleep 1
    done
    flag=0
}

trap "ctrl_c" INT

flag=0
while ! [ $age ]; do
    echo -n "Enter your age> "
    while [ $flag -eq 0 ] && ! [ $age ]; do
        # Wait for one second
        read -t 1 age
    done
    if [ $flag -eq 1 ]; then trap_menu; fi
done
JuniorCompressor
  • 19,631
  • 4
  • 30
  • 57
1

Signals are always blocked while the interrupt handler is running. In this code your handler never returns, so the signal remains blocked.

Signal handlers that don't return are generally a bad idea. If you just want to restart a function after a signal then you can run it in a subshell within a loop and have the handler just exit, which will exit from the subshell only.

Edit: Updated with Adrian's suggestions.

    #!/bin/bash

    function ctrl_c() {
            clear
            echo "Trapped CTRL-C"
            echo
            for ((timer=5; timer>0; timer--)); do
                    printf "\rRetunring to main menu in $timer seconds"
                    sleep 1
            done
            exit 10
    }

    function main() {
            trap ctrl_c INT
            # Doing main stuff
            echo "Exiting normally"
            exit 0;
    }

    while ( main ); (($? == 10)); do :; done
ccarton
  • 3,556
  • 16
  • 17
  • The exit just exit the program not the subshell – Seatter Feb 26 '15 at 14:43
  • @Seatter No, it will exit the subshell. Try it. I tested it before posting. Just make sure you invoke main surrounded with ( ). – ccarton Feb 26 '15 at 14:44
  • 1
    +1, this works and I was just about to post pretty much exactly the same. But I'd shorten the loop to `while (main); (($? == 10)); do :; done` which gets rid of the `( main )` outside the loop. The redundancy hurts my eyes :-) – Adrian Frühwirth Feb 26 '15 at 14:48
  • As an aside, don't use `echo` with options - it's not portable. Just use `printf "\r<...>"` instead. – Adrian Frühwirth Feb 26 '15 at 14:55
0

You are not 'returning' to the main function but are instead re-invoking it, so you are still technically inside your trap handler. If you exclude the 'main' call at the end of your handler, bash will return to the NEXT command after the one interrupted in the flow of execution and you can continue to catch further interrupts. See Catch SIGINT in bash, handle AND ignore

function main() {
    echo sleeping...
    sleep 60
    main
}

function ctrl_c() {
    echo "** Trapped CTRL-C"
}

trap ctrl_c INT

main

In your case, if it is simply a read operation (to get a menu selection from the user) that failed, then just check if the input was empty and loop back around for more input.

Community
  • 1
  • 1
John Rix
  • 6,271
  • 5
  • 40
  • 46