防止 SIGINT 中断函数调用和子进程

防止 SIGINT 中断函数调用和子进程

考虑以下脚本:

#!/bin/bash

set -o pipefail
set -o history

trapper() {
   func="$1" ; shift
   for sig ; do
      trap "$func $sig" "$sig"
   done
}

err_handler () {
  case $2 in
    INT)
       stop_received=1
    ;;
    TSTP)
    ;;
    ERR)
       if [[ $2 != "INT" ]]; then # for some reason, ERR gets triggered on SIGINT
          code=$?
          if [ $code -ne 0 ]; then
             echo "Failed on line $1"
             echo "$BASH_COMMAND returned $?"
             echo "Content of variables at the time of failure:"
             echo "$(set -o posix; set)"
             exit 1
          fi
       fi
    ;;
  esac
}

main() {
   ping -c 5 www.google.com # this is a test to see if INT interrupts main()
   # do a bunch of stuff, I mean a real bunch of stuff, like check some
   # files, do some processing, connect to a database,
   # do more processing, move some files around, you get the drift
}

exec > >(tee -a my.log)
exec 2>&1

trapper 'err_handler $LINENO' INT TSTP ERR
while main
do
  if [[ "$stop_received" == "1" ]]; then
     break
  fi
  setsid sleep 2 & wait
done
trap ERR

我想要完成的是在无限循环中运行脚本,直到函数 main() 返回一些非零值,即发生一些错误,或收到 SIGINT。

但是,我不希望 SIGINT 停止 main() 执行,换句话说,如果脚本收到 SIGINT,它应该等待 main() 完成,然后很好地退出。但是当我按 CTRL+C 时,我可以看到 ping 被中断。目前,我注释掉了 main() 下的所有内容,只是为了看看这是否有效。由于 ping 被中断,我假设 main() 下的其他命令也会被中断。当 ping 中断时,处理会跳转到检查 $stop_received=1 的行,然后循环中断并退出脚本。如果我仅用 echo 替换中断,则脚本将继续执行 while 主循环的下一次迭代。

如何阻止 SIGINT 中断当前正在运行的命令?由于我的脚本做了很多事情,包括数据库中的 DML 语句,中断 main() 会导致很多麻烦。

其次,该脚本也不捕获 ctrl+z。或者更确切地说,脚本只是卡在 ctrl+z 上,需要杀死 pid 来终止。我认为 sleep 是 bash 的子项,而不是脚本本身,ctrl+z 会导致脚本暂停,使 sleep 陷入困境。因此setsid并等待睡眠,但它仍然挂起。

谢谢。

答案1

有几种方法可以消除Ctrl+的效果C

  • 更改终端设置,使其不生成信号。
  • 阻塞信号,以便在信号解除阻塞时保存信号以供以后传送。
  • 忽略该信号,或为其设置处理程序。
  • 在后台进程组中运行子进程。

由于您想检测Ctrl+C已被按下,因此忽略该信号是不行的。您可以更改终端设置,但随后您需要编写自定义按键处理代码。 Shell 不提供信号阻塞的访问权限。

但是,您可以通过在单独的进程组中运行子进程来隔离子进程自动接收信号。默认情况下,交互式 shell 在单独的进程组中运行后台命令,但非交互式 shell 在同一进程组中运行它们,并且前台进程组中的所有进程都会接收来自终端事件的信号。告诉 shell在单独的进程组中运行后台作业,运行set -m。运行是强制在单独的进程组中运行setsid ping …的另一种方法。ping

set -m
interrupted=
trap 'echo Interrupted, but ping may still be running' INT
set -m
ping … &
while wait; [ $? -ge 128 ]; do echo "Waiting for background jobs"; done
echo ping has finished

如果您想要Ctrl挂起Z后台进程组,则需要从 shell 传播信号。

对于 shell 脚本来说,精细地控制信号有点困难,当遇到极端情况时,除 ATT ksh 之外的 shell 往往会出现一些问题,因此请考虑使用一种可以提供更多控制的语言,例如 Perl、Python 或 Ruby。

答案2

禁止中断程序:

陷阱“” ERR HUP INT QUIT TERM TSTP TTIN TTOU

但是,如果子命令自行处理陷阱,并且该命令必须真正完成,则需要防止向其传递信号。

对于不介意安装额外命令的 Linux 用户,您可以使用:

等待[命令]

或者,您可以调整waitFor最新源码根据需要添加到您的程序中,或者使用以下代码吉尔斯的回答。尽管这样做的缺点是无法从上游更新中受益。

请注意,其他终端和服务管理器仍然可以终止“命令”。如果您希望服务管理器无法关闭“命令”,则应将其作为具有适当终止模式和终止信号集的服务运行。

相关内容