Bash:如何在按下任意键的情况下结束无限循环?
我需要写一个无限循环,当按下任何键时,循环停止 不幸的是,这个只有在按键时才会循环 好主意吗Bash:如何在按下任意键的情况下结束无限循环?,bash,while-loop,Bash,While Loop,我需要写一个无限循环,当按下任何键时,循环停止 不幸的是,这个只有在按键时才会循环 好主意吗 #!/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." ex
#!/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
通常我不介意用一个简单的CTRL-C来打破bash无限循环。这是终止
尾部的传统方法,例如-f
。read
有许多字符参数-n
和一个可以使用的超时参数-t
发件人:
-nnchars
读取在读取nchars字符后返回,而不是等待完整的输入行,但如果在分隔符之前读取的nchars字符少于nchars,则接受分隔符
-t超时
如果在超时秒内未读取完整的输入行(或指定数量的字符),则导致读取超时并返回失败。超时可以是小数点后带有小数部分的十进制数。仅当读取是从终端、管道或其他特殊文件读取输入时,此选项才有效;从常规文件读取时,它没有任何效果。如果读取超时,read会将读取的任何部分输入保存到指定的变量名中。如果timeout为0,则read立即返回,而不尝试读取任何数据。如果输入在指定的文件描述符上可用,则退出状态为0,否则为非零。如果超过超时,则退出状态大于128
但是,读取内置使用具有自己设置的终端。因此,正如其他答案所指出的,我们需要使用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
您需要将标准输入置于非阻塞模式。下面是一个有效的示例:
#!/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
以正确捕捉返回键,使用cat-v
而不是读取
以捕捉空格
如果数据传输足够快,则cat
可能读取多个字符;如果不是所需的行为,则将cat-v
替换为dd bs=1 count=1 status=none | cat-v
编辑2019/09/05:使用
stty--save
恢复TTY设置。这里是另一个解决方案。它适用于任何按下的键,包括空格、回车、箭头等
在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
变量,因为它是那里的默认变量。我找到了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"
运行Pure:无人参与的用户输入循环
我不用和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毫秒)
- 对光标左键、光标右键、光标向上键和光标向下键做出反应
- 使用Escape或q键退出循环
“x$variable”=“x”
,而不是更简单的“$variable”=”
?有什么好处吗?或者这只是人们学习的方式吗?@Thor84no这是对旧的、有缺陷的系统的一种保护措施:几乎可以使用任何键:返回键和空格键显然没有被检测到(这里是OS X 10.10)。想法?你将如何恢复标准输入模式?哦,但我认为最好使用stty--save
保存原始状态并在以后恢复。就像这样同时!read-t0;做回显。;完成;阅读echo Finished
,但在按下Enter(或Ctrl-d)之前,它不会完成,即使使用可能的-s
选项,它也会回显输入,并且不考虑可能的-d
选项。(GNU bash,版本4.3.11)正如@jarno所提到的,这个答案是不正确的,因为read-t0
只在按下Enter键时才将输入视为可用。@hackerb9谢谢,我添加了-n参数以避免需要Enter键。它可能需要非零超时才能工作,像这样:echo-nx | read-t0.001-n1&&echo再次捕捉到它
,@jarno是正确的,尽管该解决方案的副作用是无限循环将在每次迭代中减慢1ms。Paul,我建议您将答案改为显示实际的bash而不是伪代码,这样您就可以验证它是否有效了。这不会中断一个循环,而是会中断整个过程script@mouviciel:没错,但是,如果在不退出整个脚本的情况下添加一些有关使用“trap foo SIGINT”捕获^C的信息,那就更好了。如果循环只等待按键,最好在while循环中添加例如sleep 0.1
,这样循环就不会占用CPU核心的所有可用资源。