Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash script catch signal but wait afterwards for processes to terminate

currently I'm writing a bash script like this:

foo(){
  while true
  do
    sleep 10
  done
}

bar(){
  while true
  do
    sleep 20
  done
}

foo &
bar &

wait

(I know there is no point in such a script, it's just about the structure)

Now I want to add signal handling with trap -- <doSomething> RTMIN+1. This works at first. When the script receives the rtmin+1 signal it does doSomething but afterwards it exists (with the 163 exit code, which is the number of the signal being sent).

This is not the behavior I want. I want that after receiving the signal, the script continues to wait for the processes (in this case the two functions) to terminate (which of course will not happen in this case, but the script should wait).

I tried it with adding a ; wait to the things that should be done when receiving the signal, but this does not help (or I'm doing something wrong).

Does anyone know how to achieve the desired behavior?

Thanks in advance and with best wishes.

EDIT: Maybe a more precise example helps:

clock(){
        local prefix=C
        local interval=1
        while true
        do
                printf "${prefix} $(date '+%d.%m %H:%M:%S')\n"
                sleep $interval
        done
}

volume(){
        prefix=V
                volstat="$(amixer get Master 2>/dev/null)"

                echo "$volstat" | grep "\[off\]" >/dev/null && icon="" #alternative: deaf:  mute: 

                vol=$(echo "$volstat" | grep -o "\[[0-9]\+%\]" | sed "s/[^0-9]*//g;1q")

                if [ -z "$icon" ] ; then
                if [ "$vol" -gt "50" ]; then
                        icon=""
                #elif [ "$vol" -gt "30" ]; then
                #       icon=""
                else
                        icon=""
                fi
                fi

                printf "${prefix}%s %3s%%\n" "$icon" "$vol"
}

clock &
volume &

trap -- "volume" RTMIN+2

wait

Now the RTMIN+2 signal should rerun the volume function, but the clock process should not be interrupted. (Up to now, the whole script (with all subprocesses) is terminated upon the receiving of the signal)

like image 869
atticus Avatar asked Mar 02 '23 12:03

atticus


2 Answers

When invoked with no operands, wait exits with either 0; which means all process IDs known by the invoking shell have terminated, or a value greater than 128; which means a signal for which a trap has been set is received. So, looping until wait exits with 0 is enough to keep the script alive after receiving a signal. You may also check whether its exit status is less than 128 within the loop, but I don't think that's necessary.

However, if you're sending those signals using pkill, the jobs started at the background will receive them too since child processes inherit the process name from their parent but not the custom signal handlers, and pkill signals all processes whose names match. You need to handle that case as well.

A minimal, working example would be:

#! /bin/bash -
# ignore RTMIN+1 and RTMIN+2 temporarily
# to prevent pkill from killing jobs
trap '' RTMIN+1 RTMIN+2

# start jobs
sleep 20 && echo slept for 20 seconds &
sleep 30 && echo slept for 30 seconds &

# set traps
trap 'echo received rtmin+1' RTMIN+1
trap 'echo received rtmin+2' RTMIN+2

# wait for jobs to terminate
until wait; do
  echo still waiting
done

It's also worth to note that ignoring RTMIN+1 and RTMIN+2 before starting jobs prevents shells descending from them from trapping/reseting those signals. If that is a problem, you may set an empty trap within jobs as F. Hauri suggested; or you may totally drop ignoring and use pkill with -o option to send the signal to the oldest matching process.

References:

  • Shell Command Language § Signals and Error Handling
  • wait spec. § EXIT STATUS

Related:

  • Reliably kill sleep process after USR1 signal
like image 50
oguz ismail Avatar answered Mar 05 '23 14:03

oguz ismail


Something rewritted

In order to avoid some useless forks.

clock(){  local prefix=C interval=2
    trap : RTMIN{,+{{,1}{1,2,3,4,5},6,7,8,9,10}}
    while :;do
        printf "%s: %(%d.%m %H:%M:%S)T\n" $prefix -1
        sleep $interval
    done
}

volume(){  local prefix=V vol=() field playback val foo
    while IFS=':[]' read field playback val foo;do
        [ "$playback" ] && [ -z "${playback//*Playback*}" ] && [ "$val" ] &&
            vol+=(${val%\%})
    done < <(amixer get Master)
    suffix='%%'
    if [ "$vol" = "off" ] ;then
        icon="" #alternative: deaf:  mute: 
        suffix=''
    elif (( vol > 50 )) ;then  icon=""
    elif (( vol > 30 )) ;then  icon=""
    else                       icon=""
    fi
    printf -v values "%3s$suffix " ${vol[@]}
    printf "%s%s %s\n" $prefix "$icon" "$values"
}

clock & volume &

trap volume RTMIN+2
trap : RTMIN{,+{{,1}{1,3,4,5},6,7,8,9,10,12}}
echo -e "To get status, run:\n  kill -RTMIN+2 $$"

while :;do wait ;done

Regarding my last comment about stereo bug, there is a volume function working for stereo, mono or even quadra:

volume(){
    local prefix=V vol=() field playback val foo
    local -i overallvol=0
    while IFS=':[]' read field playback val foo ;do
        [ "$playback" ] && [ -z "${playback//*Playback*}" ] && [ "$val" ] && {
            vol+=($val)
            val=${val%\%}
            overallvol+=${val//off/0}
        }
      done < <(
        amixer get Master
    )
    overallvol=$overallvol/${#vol[@]}
    if (( overallvol == 0 )) ;then
        icon=""
      elif (( overallvol > 50 )) ;then
        icon=""
      elif (( overallvol > 30 )) ;then
        icon=""
      else
        icon=""
    fi
    printf "%s%s %s\n" $prefix "$icon" "${vol[*]}"
}

or even:

volume(){
    local prefix=V vol=() field playback val foo icons=(⏻ ¼ ¼ ¼ ½ ½ ¾ ¾ ¾ ¾ ¾)
    local -i overallvol=0
    while IFS=':[]' read field playback val foo ;do
        [ "$playback" ] && [ -z "${playback//*Playback*}" ] && [ "$val" ] && {
            vol+=($val)
            val=${val%\%}
            overallvol+=${val//off/0}
        }
      done < <(
        amixer get Master
    )
    overallvol=$overallvol/${#vol[@]}
    printf "%s%s %s\n" $prefix "${icons[(9+overall)/10]}" "${vol[*]}"

Some explanations

Regarding useless forks in volume() function

I've posted there some ideas to improve the job, reducing resource eating and doing same job of choosing an icon as function of current volume set.

About while :;do wait;done loop

As requested sample stand for an infinite loop in backgrounded sub function, the main script use same infinite loop.

But as question title stand for wait afterwards for processes to terminate, I have to agree with oguz-ismail's comment.

In fact, last line would better be written:

until wait;do :;done

For more information on how wait command work and good practice, please have a look on good oguz-ismail's answer

like image 29
F. Hauri Avatar answered Mar 05 '23 16:03

F. Hauri