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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 20:16:54  来源:igfitidea点击:

Bash: How to end infinite loop with any key pressed?

bashwhile-loop

提问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 -icrnlflag to sttyto properly catch the Return key, use cat -vinstead of readin order to catch Space.

编辑 2014/12/09:添加-icrnl标志以stty正确捕获 Return 键,使用cat -v而不是read为了捕获 Space。

It is possible that catreads more than one character if it is fed data fast enough; if not the desired behaviour, replace cat -vwith dd bs=1 count=1 status=none | cat -v.

cat如果输入的数据足够快,可能会读取多个字符;如果不是所需的行为,请替换cat -vdd bs=1 count=1 status=none | cat -v.

Edit 2019/09/05:Use stty --saveto restore the TTY settings.

编辑2019年9月5日:使用stty --save恢复TTY设置。

回答by Paul

readhas a number of characters parameter -nand a timeout parameter -twhich 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 -ffor 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 REPLYvariable for the readcommand, 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

运行这个脚本