【问题标题】:Catch SIGINT in bash, handle AND ignore在 bash 中捕获 SIGINT,处理并忽略
【发布时间】:2021-05-12 15:02:35
【问题描述】:

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

我知道我可以忽略 SIGINT

trap '' SIGINT

我也可以在信号上做一些事情

trap handler SIGINT

但这仍然会在handler 执行后停止脚本。例如

#!/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,但 bash 将跳过 wait BEFORE 程序正确关闭并在其信号处理程序中创建其输出。

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

while kill -0 $PID > /dev/null 2>&1
do
    wait $PID
done

这实际上对我有用,我只是不能 100% 确定这是“干净”还是“肮脏的解决方法”。

【问题讨论】:

    标签: bash


    【解决方案1】:

    trap 将从处理程序返回,但调用处理程序时调用的命令。

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

    trap 'echo "Be patient"' INT
    
    for ((n=20; n; n--))
    do
        sleep 1
    done
    

    【讨论】:

    • 如果触发了陷阱(为此,您需要将sleep 1替换为sleep 1 & wait),按Control-C后循环退出;在陷阱返回后,您不会在信号发生的地方继续运行。
    • @anishsane 2. 用户想要什么?我认为用户想要捕获 SIGINT 并保持对他的脚本的控制。
    • 请注意,我更改了原始示例以反映我的真实用例。实际上你的回答对我有帮助,但我不确定这是否很干净。
    • 我就把这个留在这里...$ echo {20..1} # gives 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 或者let i=0; while [ $i -lt 20 ]; do sleep 1; let i=$i+1; done
    • 或者用更类似unix的方式完成Luc注释:你可以使用seq:$(seq 20 -1 1)
    【解决方案2】:

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

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

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

    1. 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 命令之前检查后台进程是否存在。

    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
    

    但这里也说一下,我们在后台发送program 后遇到了问题。问题是program继承了trap '' 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"
    

    希望这会有所帮助。 祝大家玩的开心。

    【讨论】:

      猜你喜欢
      • 2021-10-17
      • 1970-01-01
      • 2016-07-12
      • 2011-11-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-22
      • 2011-02-02
      相关资源
      最近更新 更多