Using 'Trap' command

Hi,

Can somebody share how to use while loop inside the trap command list.
I know commands must be separated by ; but how to use While loop.

I am writing this code line to trap at exit of script:

trap "echo \"While you exit this script\" ; while(1) do; <something> ; done " exit

Shows Syntax error.

Thanks

@dextergenious , a simple online search for 'trap command example' will return 00's of examples rather than just asking in here :frowning:

#!/bin/bash

trap 'echo "SIGINT (^C) signal caught at $(date)"; exit' SIGINT
while true
do
    echo "its $(date +"%Y-%h-%m %H:%M:%S") am doing nothin interesting here .... now for a sleep"
    sleep 2
done

1 Like

The trap command has fairly poor syntax capabilities. It is rather better to simplify it: write your action as a function, and use trap myExitFunction EXIT.

exit is a Bash built-in command. I believe the trap label should be EXIT (I never saw these names written in anything but uppercase, so it is at least a convention).

You probably do not want to put a never-ending loop in a trap situation, but especially without any delay.

You probably want to avoid polluting any output file by sending trap messages to stderr, or a log file, or both.

You can set up multiple traps that invoke the same function, and you can pass in the trap type as an argument and use a case statement in there. Something like:

for Type in SIGINT SIGTERM SIGPIPE SIGHUP EXIT; do
    trap "myGeneralFunction ${Type}" "${Type}"
done

Be aware that (IIRC) exiting the process because of a signal will also run any EXIT trap after the first trap is completed. You might consider using trap - EXIT to avoid this.

1 Like

[quote="munkeHoller, post:2, topic:393592"]
@munkeHoller

Thanks for sharing the link. It has pretty good examples.
But I need to have while loop inside the trap block, not outside it.

And before posting I googled but didn't find even one.

May be because I am implementing a bad logic as Paul_Pedant replied (Maybe nobody has though of doing it this way).

I was trying to put control back to the script's main body after some condition is checked in the trap upon exit.(E.g reading a choice to exit or not). But I suppose this is not possible

So I was trying to hold the script running in the Trap block itself using a loop.

Sincerely,

Thanks Paul, I like your way of putting reply which has both - knowledge transfer and cautions. That's very effective

Thanks. I was a bit hasty answering, so I missed out some points:

You can make your own myTrapFunction as complicated as you need, although that might have side effects.

(a) You might get re-entered by another signal, although I would hope Bash would delay those. But then you maybe would not be able to break out of the loop from the first trap.

(b) You probably only want to ask the user for some interaction if the script is in foreground and attached to a terminal (not a pipe or a file or /dev/null). Test with [[ -t 0 ]].

(c) If myTrapFunction is separate, you can test it properly by itself without having to throw signals at it. Conversely, you can just test the traps with simple printf's first.

As you can probably guess, I have made every error in the book (and a few more). The trick is to only make each of them once, if you can recognise them in disguise.

I'm not sure what you are trying to achieve on EXIT is actually possible.

The Bash Reference Manual states:

If a sigspec is 0 or EXIT, arg is executed when the shell exits.

My understanding of that is: as soon as your trapExitFunction returns, the process has ended. There is no place where the shell can reasonably resume running your script.

I also suspect your script will only ever get one EXIT event, for similar reasons. So you need some other input or event to break your while loop.

I will play a little with this, and post anything I find.

OK, that was fun. Three discoveries:

(1) If you set a trap for every known signal, you have run out of ways to signal the process at all. All you have left is SIGKILL, which the process can never know has happened.

(2) SIGTTIN (and probably SIGTTOUT) are persistent. Once you have some tty input available, you get hundreds (maybe thousands) of SIGTTIN per second (I was using a read -r -t 1.0 to slow down my while loop, and that did not seem to make any difference.)

(3) EXIT is not a Signal in any sense (or perhaps in two senses).

(a) In the kill(2) system call, sending a kill 0 checks the nominated process exists, and checks that you would have permission to signal that process for real, if you wanted to. But signal 0 itself cannot be sent to any process.

(b) Therefore, Bash fakes it up. When your process is about to stop running, and you have put in a trap command EXIT, Bash runs that command before it stops running your script. No actual signal is involved.

Thanks Paul for getting that for me.

Yes , you may be right, there may be no way to resume script once exit trap block returns .

Just to experiment I thought of using trap block to hold the script executing until a condition gets true, so I was calling a function containing infinite while loop taking user choice to quit or not, But it didn't work .

And now you have also shared manual citing that it only executes after exiting the shell.
So trap code block is only to do something at exit and Not to return to script again and ... that makes sense.

Do share if you like to share some thing more on this.

Thanks for all your help,

I have packed everything I know about Bash traps into 80 lines of code (fairly well commented). It shows how to trap signals, how to take action on them, and how to deal with deferring the exit from the script.

It throws test signals at itself via child processes, but you can also send your own signals from another terminal. You might find parts of it helpful in addressing your own problem. It is a heck of a lot to unpack, though: ask if you get stuck.

Don't worry about the wdog part: that is just a thing that synchronises the script to a minute, so the various delays are easier to read.

This is the Bash script.

EDIT: I found the bug that produces runaway timestamps. I use two read -t commands to slow down the loops, and as a hook for getting user input. If stdin is at EOF (redirected from /dev/null, Ctrl-D at terminal, etc), the timeout fails, and the loop runs about 6000 iterations per second. Fix is to check status from the read -t, and unless -gt 128 do the equivalent sleep (as applied below). Probably SIGTTIN would work now, too.

#! /bin/bash --
#.. trapPlay: an investigation into Bash signals.

intCount=0  #.. GLOBAL: Three Ctrl-C will exit the script.

#.. This function is called from every trap, with the trap name as an argument.
GlobalTrap () {

    #.. Report all trap calls.
    printf 'GlobalTrap: %-6s at %(%T)T\n' "${1}" -1

    case "${1}" in
    (INT)   (( ++intCount ))    #.. Console Ctrl-C.
            printf '%(%T)T (Ctrl-C) (%s)\n' -1 "${intCount}"
            #.. If exiting via Ctrl-C, disable trap for EXIT.
            (( intCount >= 3 )) && { trap - EXIT; exit; }
            ;;
    (WINCH) local Wr Wc     #.. Window change size.
            IFS='' read -r Wr < <( tput lines )
            IFS='' read -r Wc < <( tput cols  )
            printf 'Window now %s lines %s cols\n' "${Wr}" "${Wc}"
            ;;
    (EXIT)  printf "\nBash time expired -- ready to exit\n"
            Finishing;;
    esac
}

#.. This function can be used to do things after Bash has seen "exit".
#.. When you leave this function, Bash will terminate your process.
#.. You get 20 more seconds to do stuff, or type 'Y'.
Finishing () {

    local X
    while (( SECONDS <= Expired + 20 )); do
        printf 1>&2 "[Y] > "
        read -r -t 2.0 X    #-- Using 'read' as a loop delay.
        [[ "${?}" -gt 128 ]] || sleep 2.0
        [[ "${X}" = "Y" ]] && { printf "Exit confirmed\n"; exit; }
    done
    printf "\nExit overdue !!\n"
    #.. Returning from this function ends processing of the EXIT trap,
    #.. so Bash terminates the process.
}

#### Script Body Starts Here.
#.. Make an array of all the non-realtime signal numbers.
for c in {1..31}; do Cd[$c]="$c"; done

#.. Give usual names to all the important ones.
Cd[ 0]="EXIT"; Cd[ 1]="HUP";  Cd[ 2]="INT";  Cd[ 3]="QUIT";
Cd[ 5]="TRAP"; Cd[ 6]="ABRT"; Cd[10]="USR1"; Cd[12]="USR2";
Cd[13]="PIPE"; Cd[15]="TERM"; Cd[28]="WINCH";

#.. Destroy any problem ones.
for s in 5 21 22; do unset Cd["${s}"]; done

#.. Make a lot of traps like: trap 'GlobalTrap TERM' TERM
for s in "${Cd[@]}"; do
    trap "GlobalTrap ${s}" "${s}"
done

    printf '#### Send signals to me from another terminal ####\n'
    printf '#### Like: kill -HUP %s\n\n' ${$}

    Expired="$(( SECONDS + 60 ))"
    printf 'Tick: %(%T)T\n' -1

    #.. Set up some self-test shots.
    for s in {20..58..9}; do
        ( sleep "${s}" && kill -SIGUSR2 $$ ) &
    done

    ( sleep 12 && kill -SIGPIPE $$ ) &
    ( sleep 36 && kill -SIGTERM $$ ) &

    #.. Do useful work here: we just show ticks using 'read' as a loop delay.
    while (( SECONDS <= Expired )); do
        read -r -t 10.0 Discard
        [[ "${?}" -gt 128 ]] || sleep 10.0
        printf 'Tick: %(%T)T\n' -1
    done
    #.. Dropping through here triggers the EXIT trap, which runs Finishing.

There are three executions.

(1) This run shows both the 60 seconds and the 20 seconds expiring, and the self-tests. The additional HUP signal at 20:59:30 was manually entered from another terminal.

$ wdog -d 0 -w 1m; ./trapPlay
wdog       24.172| Wed Mar 20 20:59:00.000 2024
wdog 20:59:00.023| Tick
#### Send signals to me from another terminal ####
#### Like: kill -HUP 18610

Tick: 20:59:00
Tick: 20:59:10
GlobalTrap: PIPE   at 20:59:12
Tick: 20:59:20
GlobalTrap: USR2   at 20:59:20
GlobalTrap: USR2   at 20:59:29
Tick: 20:59:30
GlobalTrap: HUP    at 20:59:30
GlobalTrap: TERM   at 20:59:36
GlobalTrap: USR2   at 20:59:38
Tick: 20:59:40
GlobalTrap: USR2   at 20:59:47
Tick: 20:59:50
GlobalTrap: USR2   at 20:59:56
Tick: 21:00:00
Tick: 21:00:10
GlobalTrap: EXIT   at 21:00:10

Bash time expired -- ready to exit
[Y] > [Y] > [Y] > [Y] > [Y] > [Y] > 
Exit overdue !!
$

(2) This run show processing of dragging the terminal window to change the size, and then invoking the three Ctrl-C signals to terminate the script, bypassing the EXIT trap which would run Finishing.

$ wdog -d 0 -w 1m; ./trapPlay
wdog       13.380| Wed Mar 20 21:01:00.000 2024
wdog 21:01:00.014| Tick
#### Send signals to me from another terminal ####
#### Like: kill -HUP 18631

Tick: 21:01:00
GlobalTrap: WINCH  at 21:01:05
Window now 58 lines 97 cols
GlobalTrap: WINCH  at 21:01:06
Window now 58 lines 92 cols
GlobalTrap: WINCH  at 21:01:06
Window now 58 lines 88 cols
Tick: 21:01:10
GlobalTrap: PIPE   at 21:01:12
^CGlobalTrap: INT    at 21:01:15
21:01:15 (Ctrl-C) (1)
^CGlobalTrap: INT    at 21:01:16
21:01:16 (Ctrl-C) (2)
Tick: 21:01:20
^CGlobalTrap: INT    at 21:01:20
21:01:20 (Ctrl-C) (3)
$

(3) This run has no extra interrupts, and it shows me entering the Y at the second [Y] > prompt, to terminate the "Finishing" timeout early.

paul@paul-RV415-RV515 ~ $ wdog -d 0 -w 1m; ./trapPlay
wdog        6.047| Wed Mar 20 21:02:00.000 2024
wdog 21:02:00.006| Tick
#### Send signals to me from another terminal ####
#### Like: kill -HUP 18662

Tick: 21:02:00
Tick: 21:02:10
GlobalTrap: PIPE   at 21:02:12
GlobalTrap: USR2   at 21:02:20
Tick: 21:02:20
GlobalTrap: USR2   at 21:02:29
Tick: 21:02:30
GlobalTrap: TERM   at 21:02:36
GlobalTrap: USR2   at 21:02:38
Tick: 21:02:40
GlobalTrap: USR2   at 21:02:47
Tick: 21:02:50
GlobalTrap: USR2   at 21:02:56
Tick: 21:03:00
Tick: 21:03:10
GlobalTrap: EXIT   at 21:03:10

Bash time expired -- ready to exit
[Y] > [Y] > Y[Y] > 
Exit confirmed
$

Much Thanks Paul for all this elaborated code. This will be really really helpful.
Surely many questions will come up in my mind .

Sincerely,