Bash:如何通过按下任意键来结束无限循环?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5297638/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Bash: How to end infinite loop with any key pressed?
提问by janmartin
I need to write an infinite loop that stops when any key is pressed.
我需要编写一个无限循环,在按下任何键时停止。
Unfortunately this one loops only when a key is pressed.
不幸的是,只有在按下某个键时才会循环。
Ideas please?
请问有什么想法吗?
#!/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
回答by sam hocevar
You need to put the standard input in non-blocking mode. Here is an example that works:
您需要将标准输入置于非阻塞模式。这是一个有效的示例:
#!/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
Edit 2014/12/09:Add the -icrnl
flag to stty
to properly catch the Return key, use cat -v
instead of read
in order to catch Space.
编辑 2014/12/09:添加-icrnl
标志以stty
正确捕获 Return 键,使用cat -v
而不是read
为了捕获 Space。
It is possible that cat
reads more than one character if it is fed data fast enough; if not the desired behaviour, replace cat -v
with dd bs=1 count=1 status=none | cat -v
.
cat
如果输入的数据足够快,可能会读取多个字符;如果不是所需的行为,请替换cat -v
为dd bs=1 count=1 status=none | cat -v
.
Edit 2019/09/05:Use stty --save
to restore the TTY settings.
编辑2019年9月5日:使用stty --save
恢复TTY设置。
回答by Paul
read
has a number of characters parameter -n
and a timeout parameter -t
which could be used.
read
有许多字符参数-n
和一个-t
可以使用的超时参数。
From bash manual:
从bash 手册:
-nnchars read returns after reading nchars characters rather than waiting for a complete line of input, but honors a delimiter if fewer than nchars characters are read before the delimiter.
-ttimeout
Cause readto time out and return failure if a complete line of input (or a specified number of characters) is not read within timeout seconds. timeout may be a decimal number with a fractional portion following the decimal point. This option is only effective if read is reading input from a terminal, pipe, or other special file; it has no effect when reading from regular files. If read times out, read saves any partial input read into the specified variable name. If timeout is 0, read returns immediately, without trying to read any data. The exit status is 0 if input is available on the specified file descriptor, non-zero otherwise. The exit status is greater than 128 if the timeout is exceeded.
-nnchars read 在读取 nchars 个字符后返回,而不是等待完整的输入行,但如果在分隔符之前读取的字符少于 nchars 个字符,则使用分隔符。
-t超时
如果在超时秒内未读取完整的输入行(或指定数量的字符),则会导致读取超时并返回失败。timeout 可以是一个十进制数,小数点后面有小数部分。此选项仅在 read 从终端、管道或其他特殊文件读取输入时有效;从常规文件读取时它不起作用。如果读取超时,则读取将读取的任何部分输入保存到指定的变量名称中。如果超时为 0,则 read 立即返回,不尝试读取任何数据。如果输入在指定的文件描述符上可用,则退出状态为 0,否则为非零。如果超时,则退出状态大于 128。
However, the read builtin uses the terminal which has its own settings. So as other answers have pointed out we need to set the flags for the terminal using stty
.
但是, 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
回答by mouviciel
Usually I don't mind breaking a bash infinite loop with a simple CTRL-C. This is the traditional way for terminating a tail -f
for instance.
通常我不介意用简单的 CTRL-C 打破 bash 无限循环。这是终止tail -f
实例的传统方式。
回答by YSN
Here is another solution. It works for any key pressed, including space, enter, arrows, etc.
这是另一种解决方案。它适用于任何按下的键,包括空格、回车、箭头等。
The original solution tested in bash:
在 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
An improved solution tested in bash and dash:
在 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
In bash you could even leave out REPLY
variable for the read
command, because it is the default variable there.
在 bash 中,您甚至可以省略命令的REPLY
变量read
,因为它是那里的默认变量。
回答by DogeCode
I found this forum postand rewrote era
's post into this pretty general use format:
我找到了这个论坛帖子并将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"
Run this script
运行这个脚本