Bash 脚本捕获信号但随后等待进程终止
Bash script catch signal but wait afterwards for processes to terminate
目前我正在编写这样的 bash 脚本:
foo(){
while true
do
sleep 10
done
}
bar(){
while true
do
sleep 20
done
}
foo &
bar &
wait
(我知道这样的脚本没有意义,只是结构)
现在我想用 trap -- <doSomething> RTMIN+1
添加信号处理。这起初有效。当脚本收到 rtmin+1 信号时,它会执行一些操作,但之后它会存在(带有 163 退出代码,这是正在发送的信号的编号)。
这不是我想要的行为。我希望脚本在收到信号后继续等待进程(在本例中是两个函数)终止(在本例中当然不会发生,但脚本应该等待)。
我试过在收到信号时应该做的事情上加一个; wait
,但这没有帮助(或者我做错了什么)。
有谁知道如何实现所需的行为?
提前致谢并致以最良好的祝愿。
编辑:也许更精确的例子有帮助:
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
现在 RTMIN+2
信号应该重新运行 volume
函数,但是 clock
进程不应该被中断。 (至此,整个脚本(包括所有子进程)在收到信号后终止)
有些东西重写了
为了避免一些无用的分叉。
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
关于我对立体声错误的最后评论,有一个适用于立体声、单声道甚至四声道的音量功能:
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[*]}"
}
甚至:
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()
函数中的无用 fork
我已经在那里发布了一些改进工作的想法,减少资源消耗并做同样的工作选择 图标 作为当前卷集的功能。
关于while :;do wait;done
循环
作为背景子函数中无限循环的请求示例,主脚本使用相同的无限循环。
但由于问题标题代表 之后等待进程终止,我必须同意 oguz-ismail's comment。
其实最后一行最好写成:
until wait;do :;done
有关 wait
命令如何工作和良好实践的更多信息,请查看 good oguz-ismail's answer
在没有操作数的情况下调用时,wait
以 0 之一退出;这意味着调用 shell 已知的所有进程 ID 都已终止,或者 值大于 128;这意味着接收到已设置陷阱的信号。因此,循环直到 wait
退出 0
足以让脚本在收到信号后保持活动状态。你也可以在循环中检查它的退出状态是否小于128,但我认为没有必要。
但是,如果您使用 pkill
发送这些信号,在后台启动的作业也会收到它们,因为子进程从其父进程继承进程名称,而不是自定义信号处理程序,并且 pkill
向名称匹配的所有进程发出信号。你也需要处理这种情况。
一个最小的工作示例是:
#! /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
还值得注意的是,在开始作业之前忽略 RTMIN+1
和 RTMIN+2
可以防止 shell 从它们的 trapping/reseting 这些信号中下降。如果这是一个问题,您可以按照 F. Hauri 的建议在作业中设置一个 empty trap;或者您可以完全放弃忽略并使用 pkill
和 -o
选项将信号发送到最早的匹配进程。
参考文献:
相关:
目前我正在编写这样的 bash 脚本:
foo(){
while true
do
sleep 10
done
}
bar(){
while true
do
sleep 20
done
}
foo &
bar &
wait
(我知道这样的脚本没有意义,只是结构)
现在我想用 trap -- <doSomething> RTMIN+1
添加信号处理。这起初有效。当脚本收到 rtmin+1 信号时,它会执行一些操作,但之后它会存在(带有 163 退出代码,这是正在发送的信号的编号)。
这不是我想要的行为。我希望脚本在收到信号后继续等待进程(在本例中是两个函数)终止(在本例中当然不会发生,但脚本应该等待)。
我试过在收到信号时应该做的事情上加一个; wait
,但这没有帮助(或者我做错了什么)。
有谁知道如何实现所需的行为?
提前致谢并致以最良好的祝愿。
编辑:也许更精确的例子有帮助:
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
现在 RTMIN+2
信号应该重新运行 volume
函数,但是 clock
进程不应该被中断。 (至此,整个脚本(包括所有子进程)在收到信号后终止)
有些东西重写了
为了避免一些无用的分叉。
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
关于我对立体声错误的最后评论,有一个适用于立体声、单声道甚至四声道的音量功能:
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[*]}"
}
甚至:
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()
函数中的无用 fork
我已经在那里发布了一些改进工作的想法,减少资源消耗并做同样的工作选择 图标 作为当前卷集的功能。
关于while :;do wait;done
循环
作为背景子函数中无限循环的请求示例,主脚本使用相同的无限循环。
但由于问题标题代表 之后等待进程终止,我必须同意 oguz-ismail's comment。
其实最后一行最好写成:
until wait;do :;done
有关 wait
命令如何工作和良好实践的更多信息,请查看 good oguz-ismail's answer
在没有操作数的情况下调用时,wait
以 0 之一退出;这意味着调用 shell 已知的所有进程 ID 都已终止,或者 值大于 128;这意味着接收到已设置陷阱的信号。因此,循环直到 wait
退出 0
足以让脚本在收到信号后保持活动状态。你也可以在循环中检查它的退出状态是否小于128,但我认为没有必要。
但是,如果您使用 pkill
发送这些信号,在后台启动的作业也会收到它们,因为子进程从其父进程继承进程名称,而不是自定义信号处理程序,并且 pkill
向名称匹配的所有进程发出信号。你也需要处理这种情况。
一个最小的工作示例是:
#! /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
还值得注意的是,在开始作业之前忽略 RTMIN+1
和 RTMIN+2
可以防止 shell 从它们的 trapping/reseting 这些信号中下降。如果这是一个问题,您可以按照 F. Hauri 的建议在作业中设置一个 empty trap;或者您可以完全放弃忽略并使用 pkill
和 -o
选项将信号发送到最早的匹配进程。
参考文献:
相关: