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

穿精又带淫゛_ 提交于 2019-12-17 07:19:30

问题


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

回答1:


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.

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.

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




回答2:


read has a number of characters parameter -n and a timeout parameter -t which could be used.

From bash manual:

-n nchars 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.

-t timeout

Cause read to 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.

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.

#!/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



回答3:


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.




回答4:


Here is another solution. It works for any key pressed, including space, enter, arrows, etc.

The original solution tested in 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:

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.



来源:https://stackoverflow.com/questions/5297638/bash-how-to-end-infinite-loop-with-any-key-pressed

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!