The script isn't waiting for a key.
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).