捕获用户信号并继续执行

trap user signal and continue executing

#!/bin/sh
trap 'echo yo2 > $fifo' USR1

out(){ while read -r line; do echo $line; done; }

fifo=~/fifo
[ -e $fifo ] && rm $fifo
mkfifo fifo

tail -f > $fifo &
while :; do
    echo yo1 > $fifo
    sleep 1
done &

out < $fifo

我试图捕获 USR1 信号,以便回显到我在脚本中保持打开状态的 FIFO,但在收到信号后,FIFO 关闭,脚本终止。

当主进程得到SIGUSR1时,它正处于读取的中间(inside function out)。很少有事情会发生: * 挂起的读被挂起 * 陷阱被激活(将 yo2 附加到 fifo) * 恢复读取,并立即失败并返回错误代码 * while循环in out终止,脚本退出。

您可能需要考虑一个不同的解决方案,其中 'out' 方法是后台的,'main' 脚本将只处理信号请求,同时等待子进程完成

替换线路,out

out < $fifo &
# Loop forever. Add exit condition.
while true ; do wait ; done

简答

当 shell 处理带有 trap 的信号时,任何当前挂起的 read 都将失败。这就是脚本提前终止的原因。

要解决此问题,请在处理程序中设置一个标志。然后当 read 失败时,检查标志。如果设置了标志,那么您知道 read 由于信号而不是文件结束而失败,因此您应该继续执行循环。

发生了什么事?

首先,让我们稍微简化脚本并添加一些打印输出以查看发生了什么。这里是 trap-fifo1.sh:

#!/bin/sh

echo "my PID: $$"

trap handler USR1

handler() {
  echo "in 'handler'"
  echo caught_user1 > fifo
  echo "handler complete"
}

out() {
  echo "in 'out'"
  while read -r line; do
    echo $line
  done
  echo "loop terminated"
}

echo "about to invoke 'out'"
out < fifo
echo "at end of script"

要查看问题清单,我将 运行 在三个不同的终端中执行三个命令。每个终端会话用一列表示,纵轴为全球时间:

Terminal 1              Terminal 2      Terminal 3
---------------         -------------   ----------------
$ ./trap-fifo1.sh
my PID: 42436
about to invoke 'out'
                        $ cat > fifo
in 'out'
                        hi
hi
                                        $ kill -s SIGUSR1 42436
in 'handler'
handler complete
loop terminated
at end of script

当我们发送 SIGUSR1 时循环停止。这是因为在接收和处理信号时挂起的 read 失败。 (旁白:我努力为这种凭经验观察到的行为找到权威参考。我找到的最好的是 https://ss64.com/bash/trap.html but I don't know where that text comes from. I could not find it in either POSIX nor the bash manual。)

我们该如何解决?

问题是 read 中的歧义,我们不知道失败是因为 trap 还是因为文件结尾。所以我们会在handler中设置一个flag。这里是 trap-fifo2.sh:

#!/bin/sh

echo "my PID: $$"

trap handler USR1

handler_invoked=false

handler() {
  echo "in 'handler'"
  echo caught_user1 > fifo
  handler_invoked=true
  echo "handler complete"
}

out() {
  echo "in 'out'"
  while true; do
    if read -r line; then
      echo $line
    elif $handler_invoked; then
      echo "flag set, continuing"
      handler_invoked=false
    else
      echo "flag unset, stopping"
      break
    fi
  done
  echo "after while loop"
}

echo "about to invoke 'out'"
out < fifo
echo "at end of script"

现在让我们看看实际修复:

Terminal 1              Terminal 2      Terminal 3
---------------         -------------   ----------------
$ ./trap-fifo2.sh
my PID: 42562
about to invoke 'out'
                        $ cat > fifo
in 'out'
                        hello
hello
                                        $ kill -s SIGUSR1 42562
in 'handler'
handler complete
flag set, continuing
caught_user1

                        again
again
                                        $ kill -s SIGUSR1 42562
in 'handler'
handler complete
flag set, continuing
caught_user1
                        (Ctrl+D)
flag unset, stopping
after while loop
at end of script

从那里我们只需要通过添加标志并在循环中检查它来调整原始脚本,这应该很简单。