是否可以在bash中拦截SIGINT,执行某些操作,然后忽略它(保持bash运行)。
我知道我可以用
忽略SIGINTtrap '' SIGINT
我也可以用
在sigint上做点什么trap handler SIGINT
但是handler
执行后仍会停止脚本。 E.g。
#!/bin/bash
handler()
{
kill -s SIGINT $PID
}
program &
PID=$!
trap handler SIGINT
wait $PID
#do some other cleanup with results from program
当我按下ctrl + c时,将发送SIGINT to program,但bash将跳过wait
BEFORE程序正确关闭并在其信号处理程序中创建其输出。
使用@suspectus答案我可以将wait $PID
更改为:
while kill -0 $PID > /dev/null 2>&1
do
wait $PID
done
这实际上对我有用我只是不确定这是“干净”还是“肮脏的解决方法”。
答案 0 :(得分:8)
trap将从处理程序返回,但在调用处理程序时调用的命令之后。
所以解决方案有点笨拙,但我认为它可以满足要求。 trap handler INT
也可以使用。
trap 'echo "Be patient"' INT
for ((n=20; n; n--))
do
sleep 1
done
答案 1 :(得分:0)
简短的回答: bash 中的 SIGINT 可以被捕获、处理然后忽略,这里假设“忽略”意味着 bash 继续运行脚本。 处理程序所需的操作甚至可以推迟以构建一种“事务”,以便在一组语句完成其工作后触发(或“忽略”)SIGINT。
但是由于上面的示例涉及 bash 的许多方面(前台与后台行为、陷阱和等待)并且从那时起 8 年过去了,此处讨论的解决方案可能无法立即适用于所有系统,而无需进一步微调。
此处讨论的解决方案已在具有“GNU bash,版本 4.4.20(1)-release”的“Linux mint-mate 5.4.0-73-generic x86_64”系统上成功测试:
wait
shell 内置命令设计为可中断的。但是可以检查 wait
的退出状态,即 128 + 信号编号 = 130(在 SIGINT 的情况下)。
因此,如果您想欺骗并等待后台进程真正完成,您也可以这样做:wait ${programPID}
while [ $? -ge 128 ]; do
# 1st opportunity to place your **handler actions** is here
wait ${programPID}
done
但它也说我们在测试所有这些时遇到了错误/功能。问题是 wait
继续返回 130,即使在后台进程不再存在之后。文档说 wait
在进程 ID 错误的情况下将返回 127,但这在我们的测试中没有发生。
如果您也遇到此问题,请记住在 while 循环中运行 wait
命令之前检查后台进程是否存在。
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
但是这里也要说一下,我们在后台发送 program
后遇到了问题。问题是 program
继承了 trap '' SIGINT
,这意味着 SIGINT 通常被忽略,program
无法通过 trap -- '_set_SIGINT_RECEIVED' SIGINT
设置另一个处理程序。
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"
希望这会有所帮助。 祝大家玩得开心。