在 bash 中安全退出 while 循环

在 bash 中安全退出 while 循环

假设我有一个 bash 脚本,它可以执行以下操作:

while :
do
    foo
done

我希望能够从控制台运行这个脚本,并且能够在任意时间退出它,只要它发生在两次 foo 运行之间。因此,如果我按Ctrl+ C(这可能是导致脚本退出的另一个操作,Ctrl+C只是一个示例),它将在执行 foo 后的下一个可用点退出:

while :
do
    foo
    if [pressed_ctrl_c]:
        break
done

答案1

你可以尝试这种构造:

#!/bin/bash
#
INTR=
trap 'INTR=yes; echo "** INTR **" >&2' INT

while :
do
    (
        # Protect the subshell block
        trap '' INT

        # Protected code here
        echo -n "The date/time is: "
        sleep 2
        date
        read -t2 -p 'Continue (y/n)? ' YN || echo
        test n = "$YN" && echo "Asked for BREAK" >&2 && exit 90
    )
    SS=$?
    test 90 -eq $SS && echo "Matched BREAK" >&2 && break

    # Ctrl/C, perhaps?
    test yes = "$INTR" && echo "Matched INTR" >&2 && break
done
exit 0

一些笔记

  • read和对test演示了对块内受保护代码段的交互控制( ... )
  • exit 90相当于breakbut from inside a subshel​​l。紧接着子 shell 块结束之后的行test 0 != $? ...用于捕获状态exit 90并实现break代码实际想要的功能。
  • 子 shell 可以使用不同的退出状态值来指示不同类型的所需控制流(breakexit等...)
  • 这不会阻止程序安装自己的信号处理程序。例如,为( )gdb安装其自己的处理程序。如果目的是防止用户中断会话,则更改中断键可能有助于混淆情况(请参阅下面的代码)。不优雅但可能有效。SIGINTCtrlC

更改终端上的 SIGINT 键

G=$(stty -g)                    # Save settings
test -n "$G" && stty intr ^A    # That is caret and A, not Ctrl/A

# ... SIGINT generated with Ctrl/A rather than Ctrl/C ...

test -n "$G" && stty "$G"       # Restore original settings

答案2

这似乎有效:

#!/bin/sh
pressed_ctrl_c=
trap "pressed_ctrl_c=1" INT
while true
do
        (trap "" INT; foo)&
        wait  ||  wait
        if [ "$pressed_ctrl_c" ]
        then
                # echo
                break
        fi
done
  • 初始化pressed_ctrl_c为空。这在脚本中可能不是必需的。
  • trap command signum告诉 shell 设置捕获信号号signum 并执行command当它捕获一个时。 “INT” 是 “SIGINT” 的缩写,而“SIGINT” 是 “中断信号” 的缩写,这是Ctrl+生成的信号的技术术语C,因此,当您键入Ctrl+时C,shell 会将其设置pressed_ctrl_c为 1。(SIGINT 的数值为 2,因此trap "pressed_ctrl_c=1" 2如果您想节省输入时间,可以这样说。)
  • 在循环内部,我们有一个位于括号中的命令行。这将创建一个子 shell。
  • 我们trap再次使用该命令。这次command是一个空字符串;这告诉 shell 忽略信号号signum。由于它位于括号内,因此它仅影响子 shell。
  • 跑步foo。由于它是从子 shell 运行的, 因此foo将忽略Ctrl+ C,即,即使您键入它,它也会继续运行。
  • 将 subshel​​l 放在后台......
  • ...然后等待它完成。
  • 如果wait命令成功,则继续执行该if语句。如果失败,则执行另一次。 (我会回过头来讨论这一点。)
  • 如果$pressed_ctrl_c已设置,则跳出循环。echo如果您的终端上出现Ctrl+ 并且您想要移动到下一行,则可以选择取消注释该命令。C^C

我们在后台运行一个命令,然后立即wait执行它。这与在前台运行命令非常相似(至少在脚本中执行时)。wait当子 shell 终止时,该命令将成功终止;即,当foo命令终止时。 (wait即使foo返回错误,该命令也会成功终止。)当第一个wait命令成功终止时,我们跳过第二个命令并转到if.

运行循环的 shell 正在捕获中断,但子 shell 以及进程foo会忽略它们。因此,当您键入Ctrl+时C,shell 将设置pressed_ctrl_c为 1 并中止(第一个)wait命令。由于第一个wait命令失败,我们继续执行第二个命令。请记住,foo子 shell 仍在运行,因此wait仍然有一些事情要做(即,它将等待foo完成。)

最后,如果该变量已设置为指示在运行时已按下Ctrl+ ,则中断输出循环。Cfoo

如果按Ctrl+C两次,第二个将中断并中止第二个wait命令。这将导致您的脚本终止并返回到 shell 提示符,同时foo在后台运行。你可以通过说来缓解这种情况wait  ||  wait  ||  wait  ||  wait;将其延伸到您想要的程度。您需要为每个输入一次Ctrl+ 来提前终止脚本。Cwait

噢!

这样做的一个问题是,由脚本置于后台的进程将其标准输入设置为/dev/null.如果您foo从键盘读取,则以上内容需要修改。

相关内容