假设我有一个 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
相当于break
but from inside a subshell。紧接着子 shell 块结束之后的行test 0 != $? ...
用于捕获状态exit 90
并实现break
代码实际想要的功能。 - 子 shell 可以使用不同的退出状态值来指示不同类型的所需控制流(
break
、exit
等...) - 这不会阻止程序安装自己的信号处理程序。例如,为( )
gdb
安装其自己的处理程序。如果目的是防止用户中断会话,则更改中断键可能有助于混淆情况(请参阅下面的代码)。不优雅但可能有效。SIGINT
CtrlC
更改终端上的 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,即,即使您键入它,它也会继续运行。 - 将 subshell 放在后台......
- ...然后等待它完成。
- 如果
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
从键盘读取,则以上内容需要修改。