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

在没有操作数的情况下调用时,wait0 之一退出;这意味着调用 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+1RTMIN+2 可以防止 shell 从它们的 trapping/reseting 这些信号中下降。如果这是一个问题,您可以按照 F. Hauri 的建议在作业中设置一个 empty trap;或者您可以完全放弃忽略并使用 pkill-o 选项将信号发送到最早的匹配进程。

参考文献:

相关: