在bash中捕获SIGINT,处理并忽略

Zul*_*lan 28 bash

是否有可能在bash中拦截一个SIGINT,做一些事情,然后忽略它(保持bash运行).

我知道我可以忽略SIGINT

trap '' SIGINT
Run Code Online (Sandbox Code Playgroud)

我也可以用sigint做点什么

trap handler SIGINT
Run Code Online (Sandbox Code Playgroud)

但是在handler执行之后仍然会停止脚本.例如

#!/bin/bash

handler()
{
    kill -s SIGINT $PID
}

program &
PID=$!

trap handler SIGINT

wait $PID

#do some other cleanup with results from program
Run Code Online (Sandbox Code Playgroud)

当我按下ctrl + c时,将发送SIGINT to program,但bash将跳过waitBEFORE程序正确关闭并在其信号处理程序中创建其输出.

使用@suspectus答案我可以wait $PID改为:

while kill -0 $PID > /dev/null 2>&1
do
    wait $PID
done
Run Code Online (Sandbox Code Playgroud)

这实际上对我有用我只是不是100%确定这是'干净'还是'肮脏的解决方法'.

sus*_*tus 11

trap将从处理程序返回,但调用处理程序时调用该命令之后.

所以解决方案有点笨拙,但我认为它可以满足要求.trap handler INT也会工作.

trap 'echo "Be patient"' INT

for ((n=20; n; n--))
do
    sleep 1
done
Run Code Online (Sandbox Code Playgroud)

  • 请注意,我更改了原始示例以反映我的真实用例。实际上你的回答对我有帮助,但我不确定这是否很干净。 (2认同)
  • 我会把它留在这里......`$ echo {20..1}#give 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1`或者`let i = 0; 而[$ i -lt 20]; 做睡觉1; 让我= $ i + 1; done` (2认同)

Ric*_*ntz 5

简短的回答:bash 中的 SIGINT 可以被捕获、处理然后忽略,假设这里的“忽略”意味着 bash 继续运行脚本。处理程序所需的操作甚至可以推迟以构建一种“事务”,以便在一组语句完成其工作后触发(或“忽略”)SIGINT。

但由于上面的示例涉及 bash 的许多方面(前台与后台行为、陷阱和等待),并且从那时起已经过去了 8 年,因此这里讨论的解决方案可能无法立即在所有系统上工作,无需进一步微调。

这里讨论的解决方案已在具有“GNU bash,版本 4.4.20(1)-release”的“Linux mint-mate 5.4.0-73-generic x86_64”系统上成功进行了测试:

  1. shellwait内置命令被设计为可中断的。但是可以检查 的退出状态wait,即 128 + 信号编号 = 130(在 SIGINT 的情况下)。因此,如果您想欺骗并等待后台进程真正完成,也可以执行以下操作:
wait ${programPID}
while [ $? -ge 128 ]; do
   # 1st opportunity to place your **handler actions** is here
   wait ${programPID}
done
Run Code Online (Sandbox Code Playgroud)

但也要说的是,我们在测试所有这些时遇到了错误/功能。问题是,wait即使后台进程不再存在,仍然继续返回 130。文档说,wait如果进程 ID 错误,将返回 127,但在我们的测试中并没有发生这种情况。如果您也遇到此问题,请记住wait在 while 循环中运行命令之前检查后台进程是否存在。

  1. 假设以下脚本是您的program,它只是从 5 倒数到 0,并将其输出输出到名为 program.out 的文件中。这里的 while 循环被认为是一个“事务”,它不会被 SIGINT 干扰。最后一条评论:此代码在执行推迟的操作后不会忽略 SIGINT,而是恢复旧的 SIGINT 处理程序并引发 SIGINT:
#!/bin/bash
rm -f program.out

# Will be set to 1 by the SIGINT ignoring/postponing handler
declare -ig SIGINT_RECEIVED=0
# On <CTRL>+C or "kill -s SIGINT $$" set flag for [later|postponed] examination
function _set_SIGINT_RECEIVED {
    SIGINT_RECEIVED=1
}

# Remember current SIGINT handler
old_SIGINT_handler=$(trap -p SIGINT)
# Prepare for later restoration via ${old_SIGINT_handler}
old_SIGINT_handler=${old_SIGINT_handler:-trap - SIGINT}

# Start your "transaction", which should NOT be disturbed by SIGINT
trap -- '_set_SIGINT_RECEIVED' SIGINT

count=5
echo $count | tee -a program.out
while (( count-- )); do
    sleep 1
    echo $count | tee -a program.out
done

# End of your "transaction"
# Look whether SIGINT was received
if [ ${SIGINT_RECEIVED} -eq 1 ]; then
    # Your **handler actions** are here
    echo "SIGINT was received during transaction..." | tee -a program.out
    echo "... doing postponed work now..." | tee -a program.out
    echo "... restoring old SIGINT handler and sending SIGINT" | tee -a program.out
    echo "program finished after SIGINT postponed." | tee -a program.out
    ${old_SIGINT_handler}
    kill -s SIGINT $$
fi
echo "program finished without having received SIGINT." | tee -a program.out
Run Code Online (Sandbox Code Playgroud)

但这里还要说一下,我们program在后台发送后遇到了问题。问题是program继承了 atrap '' SIGINT这意味着 SIGINT 通常被忽略并且program无法通过设置另一个处理程序trap -- '_set_SIGINT_RECEIVED' SIGINT

  1. program我们通过放入一个子 shell 并在后台发送该子 shell 来解决这个问题,正如您现在将MAIN在前台运行的脚本示例中看到的那样。最后还有一条评论:在此脚本中,您可以通过变量决定ignore_SIGINT_after_handling是否最终忽略 SIGINT 并继续运行脚本,或者在处理程序操作完成其工作后执行默认的 SIGINT 行为:
#!/bin/bash

# Will be set to 1 by the SIGINT ignoring/postponing handler
declare -ig SIGINT_RECEIVED=0
# On <CTRL>+C or "kill -s SIGINT $$" set flag for later examination
function _set_SIGINT_RECEIVED {
    SIGINT_RECEIVED=1
}

# Set to 1 if you want to keep bash running after handling SIGINT in a particular way
#  or to 0 (or any other value) to run original SIGINT action after postponing SIGINT
ignore_SIGINT_after_handling=1

# Remember current SIGINT handler
old_SIGINT_handler=$(trap -p SIGINT)
# Prepare for later restoration via ${old_SIGINT_handler}
old_SIGINT_handler=${old_SIGINT_handler:-trap - SIGINT}

# Start your "transaction", which should NOT be disturbed by SIGINT
trap -- '_set_SIGINT_RECEIVED' SIGINT

    # Do your work, for eample 
    (./program) &
    programPID=$!
    wait ${programPID}
    while [ $? -ge 128 ]; do
       # 1st opportunity to place a part of your **handler actions** is here
       # i.e. send SIGINT to ${programPID} and make sure that it is only sent once
       # even if MAIN receives more SIGINT's during this loop
       wait ${programPID}
    done

# End of your "transaction"
# Look whether SIGINT was received
if [ ${SIGINT_RECEIVED} -eq 1 ]; then
    # Your postponed **handler actions** are here
    echo -e "\nMAIN is doing postponed work now..."
    if [ ${ignore_SIGINT_after_handling} -eq 1 ]; then
        echo "... and continuing with normal program execution..."
    else
        echo "... and restoring old SIGINT handler and sending SIGINT via 'kill -s SIGINT \$\$'"
        ${old_SIGINT_handler}
        kill -s SIGINT $$
    fi
fi

# Restore "old" SIGINT behaviour
${old_SIGINT_handler}
# Prepare for next "transaction"
SIGINT_RECEIVED=0
echo ""
echo "This message has to be shown in the case of normal program execution"
echo "as well as after a caught and handled and then ignored SIGINT"
echo "End of MAIN script received"
Run Code Online (Sandbox Code Playgroud)

希望这个对你有帮助。愿大家都过得愉快。