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)
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:
wait
spec. § EXIT STATUSRelated:
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[*]}"
volume()
functionI'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.
while :;do wait;done
loopAs 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With