5

I'd like to wait for keypress and exit when pressed the letter q. The script isn't waiting for a key. How to correct it?

while read line
do
    ...
    while :
    do
        read -n 1 key

        if [[ $key = q ]]
        then
            break
        fi
    done
done < $1
webb
  • 4,180
  • 1
  • 17
  • 26
xralf
  • 3,312
  • 45
  • 129
  • 200
  • See: [Read input in bash inside a while loop](http://stackoverflow.com/q/6883363/3776858) – Cyrus May 15 '16 at 19:23
  • 1
    With `break` you only terminate the inner `while` loop. With `break 2` you can terminate 2 loops (but difficult when somebody else changes the code). When you put t in a function you can use `return`. – Walter A May 15 '16 at 19:41

2 Answers2

6

read reads the input.

In your script, the input is changed to $1.

The first level while loop is reading a line from the file for which the name is stored into $1, and read -n 1 key reads and stores the first char of the next line from the same file.

Give a try to that :

while read line ; do
  while : ; do
    read -n 1 key <&1
    if [[ $key = q ]] ; then
      break
    fi
  done
done < $1

<&1 is the standard input.

Jay jargot
  • 2,745
  • 1
  • 11
  • 14
1

The script isn't waiting for a key.

  • Because the command read is getting its input from the redirected file in:

    done < $1           ### Should be "$1".
    

That file is consumed by both read commands (and anything else inside the loop that read stdin).

The correct solution for shell's read that have the option -u (and bash does), is to define the fd (file descriptor) to use in each read while the file is redirected to some fd number (greater than 2):

while read -u 3 line ; do
    while : ; do
    read -u 1 -n 1 key
    if [[ $key = q ]] ; then
        break
    fi
    done
    echo "$line"
done 3< "$1"

That makes the first read get the input from fd 3 which comes from the file (done 3< "$1"), and the second read get the input from fd 1 (stdin).

For POSIX shells, read does not have the -u option, we need to perform some redirections to get the same general effect:

#!/bin/dash

while read line <&3; do
    while : ; do
        read key <&1
        if [ "$key" = q ] ; then
            break
        fi
    done
done 3< "$1"

Sadly, this also remove the -n 1 option from read and each key from the keyboard must be followed by pressing Enter.

To actually read one character we may use dd. And we also may set the actual terminal as /dev/tty (blocks any other redirection) and if we need to hide the text typed (or passwords) use stty -echo:

#!/bin/dash
while read line <&3; do
    while : ; do
        stty raw -echo
        key=$(dd bs=1 count=1 </dev/tty 2> /dev/null)
        stty -raw echo
        if [ "$key" = q ] ; then
            break
        fi
    done
    echo "$line"
done 3< "$1"

Caveat: setting stty raw will prevent the effect of keys like CTRL-C (be careful).