我正在写一个shell脚本:
#!/bin/bash
i=1;
sp="/-\|";
no_config=true;
echo "type \"continue\" to exit this while loop";
echo "if you feel the conditions for continuing sucessfully have been met... ";
while $no_config;
do printf "\b${sp:i++%${#sp}:1}";
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false;
sleep 1;
done;
# do more stuff
我现在还想添加一个用户引发的循环退出。
这看起来很复杂。
起初我发现了这个,我的希望就起来了: https://unix.stackexchange.com/a/228152/228658 但事实证明读取命令停止执行。
我不想要这样。我想要类似手册页的内容,如果您在任何时候点击“q”,它就会退出,但不会阻止上下滚动的能力。它正在后台谨慎地等待输入。
我如何在 shell 脚本中实现这一点?
答案1
一个更简单的解决方案是通知用户按Ctrl-C
退出循环,这将发出SIGINT
信号并结束脚本。
如果你想在结束脚本之前做其他事情,你可以捕获信号:
cleanup() {
# ...
exit 0
}
trap 'cleanup' INT
或者,您可以在子 shell 中运行其中一个 shell,确保当其中一个 shell 退出时,另一个 shell 也会被终止。通过这种方式,您可以阻止其中一个输入,而另一个则执行其他检查。
#!/bin/bash
i=1
sp="/-\|"
no_config=true
echo "type \"continue\" to exit this while loop if you feel the conditions for continuing sucessfully have been met... "
(
while $no_config
do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
kill $$
) &
child_pid=$!
while $no_config
do
read -r typed_continue
[[ "$typed_continue" = "continue" ]] && no_config=false
sleep 1
done
kill $child_pid
答案2
不幸的是,Bash 不太适合这样的控制。
然而,可以通过一些方法来获得可行的东西。
从最简单到最复杂,以下是一些:
输入 Ctrl+C 退出
您可以安装trap
for Ctrl+C (通过捕获信号 INT 进行处理)以将您的no_config
值设置为,false
以便您的while
循环在 Ctrl+C 上退出:
i=1
sp='/-\|'
no_config=true
echo "type Ctrl+C to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
trap 'no_config=false' INT
while $no_config; do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
trap 'trap - INT; kill -INT $$' INT
echo "do more stuff"
(如果您从交互式 shell 运行上述代码记住用前导(
和尾随包围整个代码)
,这样您的 shell 就不会被代码的作用所污染。)
请注意,while
只有当循环内的所有命令都完成时,循环才会真正中断,因为 on 的测试$no_config
仅发生在while
.您可能想要也可能不想要这种行为,具体取决于您在循环内执行的操作。在您的示例中,唯一真正的“慢”操作是显式的sleep 1
,因此不需要突然中断,但是如果您确实想无论如何都中断循环,那么最简单的附录是使用一个子shell整体上可以被杀死。像这样:
trap 'kill $!' INT
(
i=1
sp='/-\|'
no_config=true
echo "type Ctrl+C to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
while $no_config; do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
) &
wait
trap 'trap - INT; kill -INT $$' INT
echo "do more stuff"
这里我们设置了一个trap
for INT (Ctrl+C) 来杀死子 shell,以便wait
继续“做更多的事情”部分。
但请注意,通过这种方式,循环内设置的变量不会传播到“做更多事情”部分。要解决此问题,您需要一些辅助通信,例如临时文件或命名 FIFO。
输入“q”退出
read
允许您使用任何字符退出(从而也将 Ctrl+C 保留为常规操作)的上述方法的替代方案可能是在循环中嵌入非阻塞(在 Bash v4+ 上可用)。这还需要对终端设置进行一些辅助准备。整个事情可能是这样的:
term_settings="$(stty -g)"
trap 'stty "${term_settings}"' EXIT
i=1
sp='/-\|'
no_config=true
echo "type exactly \"q\" to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
stty -icanon -echo
while $no_config; do
while read -t 0 && read -rN 1 ; do [ "${REPLY}" = q ] && break 2 || true; done
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
stty "${term_settings}"
echo "do more stuff"
在这里,我们首先保存终端的当前设置,以便稍后在退出时恢复它们,然后禁用显示键入的字符 ( -echo
) 和面向行的键盘输入 ( -icanon
)。在while
循环中,我们使用read -t 0
(非阻塞read
)来检查是否存在键入的字符,在这种情况下,我们将它们一一读取(-N 1
)以测试其中任何一个是否要q
退出循环。
read -t 0
由于循环的每次迭代都会完成,这会增加一些计算开销while
。在您的示例中,开销可以忽略不计。
如果您希望使用 a 进行突然可中断循环type q to quit
,则需要采用不同的方法,并且事情会变得更加复杂,因为您必须在后台代码中运行代码的可中断部分,同时在read
前台代码中等待 a ,这也需要read
当后台代码定期完成时解锁的一种方法。
获得这一点需要大量扩展 Bash 的能力,例如:
- 将代码的可中断部分单独封装在后台子 shell 中,有点像我们之前所做的,但需要额外注意
- 模拟 Ctrl+C 从后台子 shell 到前台
read
。
这也意味着不可能将变量从循环传递while
到“做更多事情”部分,就像我们之前所说的那样(需要有意设置辅助通信)。
一个例子可能是这样的:
#!/bin/bash
term_settings="$(stty -g)"
(
trap 'stty "${term_settings}"' EXIT
_keyboard_waiter=$BASHPID
(
i=1
sp='/-\|'
no_config=true
echo "type exactly \"q\" to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
while $no_config; do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
kill -INT $_keyboard_waiter
) &
while read -rsN 1 && ! [ "${REPLY}" = q ] ; do :; done
{ kill $! && wait; } 2>/dev/null
)
echo "do more stuff"
这里我们有:
- 由于需要处理信号,因此可以通过脚本文件更好地使用整个内容
- 在开始时保存的终端设置以便稍后在退出时恢复它们,这对于
read
中断时是必要的 - 一个子 shell 在前台运行,负责等待键盘输入
- 一个子子 shell 在后台运行 (&),用于代码的可中断部分
kill -INT
后台子 shell使用来中断read
的子 shell。这模拟了该子 shell 的 Ctrl+C,由 Bash 处理- 一种选项,
read
不显示键入的字符 (-s
),同时等待/只接受一个字符,而不管换行符 (-N 1
),加上常见-r
的不将反斜杠解释为转义字符 - 当确实键入“q”时,对后台子 shell 进行简单的“kill&wait”