Bash:如何在按下任意键的情况下结束无限循环?
Posted
技术标签:
【中文标题】Bash:如何在按下任意键的情况下结束无限循环?【英文标题】:Bash: How to end infinite loop with any key pressed? 【发布时间】:2011-07-14 22:26:02 【问题描述】:我需要编写一个无限循环,在按下任意键时停止。
不幸的是,这个只有在按下一个键时才会循环。
请给点意见?
#!/bin/bash
count=0
while : ; do
# dummy action
echo -n "$a "
let "a+=1"
# detect any key press
read -n 1 keypress
echo $keypress
done
echo "Thanks for using this script."
exit 0
【问题讨论】:
【参考方案1】:您需要将标准输入置于非阻塞模式。这是一个有效的示例:
#!/bin/bash
if [ -t 0 ]; then
SAVED_STTY="`stty --save`"
stty -echo -icanon -icrnl time 0 min 0
fi
count=0
keypress=''
while [ "x$keypress" = "x" ]; do
let count+=1
echo -ne $count'\r'
keypress="`cat -v`"
done
if [ -t 0 ]; then stty "$SAVED_STTY"; fi
echo "You pressed '$keypress' after $count loop iterations"
echo "Thanks for using this script."
exit 0
编辑 2014/12/09: 将 -icrnl
标志添加到 stty
以正确捕获 Return 键,使用 cat -v
而不是 read
以捕获空格。
如果输入数据足够快,cat
可能会读取多个字符;如果不是所需的行为,请将cat -v
替换为dd bs=1 count=1 status=none | cat -v
。
编辑 2019/09/05: 使用 stty --save
恢复 TTY 设置。
【讨论】:
我知道这有点离题了,但是为什么人们在 bash 中使用"x$variable" = "x"
之类的条件而不是更简单的"$variable" = ""
?有什么好处吗?还是人们这样做只是因为他们是这样学习的?
@Thor84no 这是针对旧的、有缺陷的系统的保护措施:***.com/a/6853353/111461
适用于几乎任何键:显然未检测到 Return 和 Space 键(此处为 OS X 10.10)。想法?
如何恢复标准输入模式?
哦,不过我觉得还是用stty --save
保存原来的状态,以后再恢复比较好。【参考方案2】:
read
有多个字符参数-n
和一个可以使用的超时参数-t
。
来自bash manual:
-n nchars read 在读取 nchars 个字符后返回,而不是等待完整的输入行,但如果在分隔符之前读取的 nchars 个字符少于此,则使用分隔符。
-t 超时
如果在 timeout 秒内没有读取完整的输入行(或指定数量的字符),则导致 read 超时并返回失败。 timeout 可以是一个小数,小数点后面有一个小数部分。此选项仅在 read 正在从终端、管道或其他特殊文件读取输入时有效;从常规文件读取时它没有效果。如果读取超时,读取将读取的任何部分输入保存到指定的变量名称中。如果 timeout 为 0,read 立即返回,而不尝试读取任何数据。如果输入在指定的文件描述符上可用,则退出状态为 0,否则为非零。超过超时退出状态大于128。
但是,内置的 read 使用具有自己设置的终端。因此,正如其他答案所指出的,我们需要使用stty
为终端设置标志。
#!/bin/bash
old_tty=$(stty --save)
# Minimum required changes to terminal. Add -echo to avoid output to screen.
stty -icanon min 0;
while true ; do
if read -t 0; then # Input ready
read -n 1 char
echo -e "\nRead: $char\n"
break
else # No input
echo -n '.'
sleep 1
fi
done
stty $old_tty
【讨论】:
像这样while ! read -t0; do echo -n .; done; read; echo Finished
,但直到按下 Enter(或 Ctrl-d)才完成,即使有可能的 -s
选项,它也会回显输入,并且不尊重可能的 -d
选项。 (GNU bash,版本 4.3.11)
正如@jarno 提到的,这个答案是不正确的,因为read -t 0
只有在按下 Enter 键后才会看到输入可用。
@hackerb9 谢谢,我添加了 -n 参数以避免需要输入键。
它可能需要非零超时才能工作,如下所示:echo -n x | read -t0.001 -n1 && echo caught it
再一次,@jarno 是正确的,尽管该解决方案的副作用是无限循环每次迭代都会减慢 1 毫秒。 Paul,我建议更改您的答案以显示实际的 bash 而不是伪代码,以便您验证它是否有效。【参考方案3】:
通常我不介意用简单的 CTRL-C 打破 bash 无限循环。例如,这是终止 tail -f
的传统方式。
【讨论】:
这不会破坏循环,它会破坏整个脚本 @mouviciel: 是的,但如果您要添加一些有关使用 'trap foo SIGINT' 捕获 ^C 而不退出整个脚本的信息会更好。【参考方案4】:纯bash:无人参与的用户循环输入
我已经完成了这项工作,而无需使用 stty:
loop=true
while $loop; do
trapKey=
if IFS= read -d '' -rsn 1 -t .002 str; then
while IFS= read -d '' -rsn 1 -t .002 chr; do
str+="$chr"
done
case $str in
$'\E[A') trapKey=UP ;;
$'\E[B') trapKey=DOWN ;;
$'\E[C') trapKey=RIGHT ;;
$'\E[D') trapKey=LEFT ;;
q | $'\E') loop=false ;;
esac
fi
if [ "$trapKey" ] ;then
printf "\nDoing something with '%s'.\n" $trapKey
fi
echo -n .
done
这会
循环占用空间非常小(最长 2 毫秒) 对 cursor left、cursor right、cursor up 和 cursor down 键做出反应 使用 Escape 或 q 键退出循环。【讨论】:
【参考方案5】:这是另一种解决方案。它适用于任何按下的键,包括空格、回车、箭头等。
在 bash 中测试的原始解决方案:
IFS=''
if [ -t 0 ]; then stty -echo -icanon raw time 0 min 0; fi
while [ -z "$key" ]; do
read key
done
if [ -t 0 ]; then stty sane; fi
在 bash 和 dash 中测试的改进解决方案:
if [ -t 0 ]; then
old_tty=$(stty --save)
stty raw -echo min 0
fi
while
IFS= read -r REPLY
[ -z "$REPLY" ]
do :; done
if [ -t 0 ]; then stty "$old_tty"; fi
在 bash 中,您甚至可以为 read
命令省略 REPLY
变量,因为它是那里的默认变量。
【讨论】:
如果您的循环除了等待按键之外什么都不做,最好添加例如sleep 0.1
在 while 循环中,这样循环不会占用 CPU 内核的所有可用资源。【参考方案6】:
我找到了this forum post 并将era
的帖子改写成这种非常通用的格式:
# stuff before main function
printf "INIT\n\n"; sleep 2
INIT()
starting="MAIN loop starting"; ending="MAIN loop success"
runMAIN=1; i=1; echo "0"
; INIT
# exit script when MAIN is done, if ever (in this case counting out 4 seconds)
exitScript()
trap - SIGINT SIGTERM SIGTERM # clear the trap
kill -- -$$ # Send SIGTERM to child/sub processes
kill $( jobs -p ) # kill any remaining processes
; trap exitScript SIGINT SIGTERM # set trap
MAIN()
echo "$starting"
sleep 1
echo "$i"; let "i++"
if (($i > 4)); then printf "\nexiting\n"; exitScript; fi
echo "$ending"; echo
# main loop running in subshell due to the '&'' after 'done'
while ((runMAIN)); do
if ! MAIN; then runMain=0; fi
done; &
# --------------------------------------------------
tput smso
# echo "Press any key to return \c"
tput rmso
oldstty=`stty -g`
stty -icanon -echo min 1 time 0
dd bs=1 count=1 >/dev/null 2>&1
stty "$oldstty"
# --------------------------------------------------
# everything after this point will occur after user inputs any key
printf "\nYou pressed a key!\n\nGoodbye!\n"
运行this script
【讨论】:
以上是关于Bash:如何在按下任意键的情况下结束无限循环?的主要内容,如果未能解决你的问题,请参考以下文章