Shell 脚本:添加“通过键入 q 退出”到 while do case 已经依赖于 if

Shell 脚本:添加“通过键入 q 退出”到 while do case 已经依赖于 if

我正在写一个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 退出

您可以安装trapfor 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"

这里我们设置了一个trapfor 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 的能力,例如:

  1. 将代码的可中断部分单独封装在后台子 shell 中,有点像我们之前所做的,但需要额外注意
  2. 模拟 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”

相关内容