The simplest way is to use /dev/tty
as the read for keyboard input.
For example:
#!/bin/bash
echo hello | while read line
do
echo We read the line: $line
echo is this correct?
read answer < /dev/tty
echo You responded $answer
done
This breaks if you don't run this on a terminal, and wouldn't allow for input to be redirected into the program, but otherwise works pretty well.
More generally, you could take a new file handle based off the original stdin, and then read from that. Note the exec
line and the read
#!/bin/bash
exec 3<&0
echo hello | while read line
do
echo We read the line: $line
echo is this correct?
read answer <&3
echo You responded $answer
done
In both cases the program looks a bit like:
% ./y
We read the line: hello
is this correct?
yes
You responded yes
The second variation allows for input to also be redirected
% echo yes | ./y
We read the line: hello
is this correct?
You responded yes
Reading one character means reading one byte at a time until you get a full character.
To read one byte with the POSIX toolchest, there's dd bs=1 count=1
.
Note however reading from a terminal device, when that device is in icanon
mode (as it generally is by default), only ever returns when you press Return (a.k.a. Enter), because until then the terminal device driver implements a form of line editor that allows you to use Backspace or other editing characters to amend what you enter, and what you enter is made available to the reading application only when you submit that line you've been editing (with Return or Ctrl+D).
For that reason, ksh
's read -n/N
or zsh
's read -k
, when they detect stdin is a terminal device, put that device out of the icanon
mode, so that bytes are available to read as soon as they are sent by the terminal.
Now note that ksh
's read -n n
only reads up to n
characters from a single line, it still stops when a newline character is read (use -N n
to read n
characters). bash
, contrary ksh93, still does IFS and backslash processing for both -n
and -N
.
To mimic zsh
's read -k
or ksh93
's read -N1
or bash
's IFS= read -rN 1
, that is, read one and only one character from stdin, POSIXly:
readc() { # arg: <variable-name>
if [ -t 0 ]; then
# if stdin is a tty device, put it out of icanon, set min and
# time to sane value, but don't otherwise touch other input or
# or local settings (echo, isig, icrnl...). Take a backup of the
# previous settings beforehand.
saved_tty_settings=$(stty -g)
stty -icanon min 1 time 0
fi
eval "$1="
while
# read one byte, using a work around for the fact that command
# substitution strips the last character.
c=$(dd bs=1 count=1 2> /dev/null; echo .)
c=${c%.}
# break out of the loop on empty input (eof) or if a full character
# has been accumulated in the output variable (using "wc -m" to count
# the number of characters).
[ -n "$c" ] &&
eval "$1=\${$1}"'$c
[ "$(($(printf %s "${'"$1"'}" | wc -m)))" -eq 0 ]'; do
continue
done
if [ -t 0 ]; then
# restore settings saved earlier if stdin is a tty device.
stty "$saved_tty_settings"
fi
}
Best Answer
So with
bash
there are (at least) two options.The first is
read -n 5
. This may sound like it meets your needs. From the man pageBUT there's a gotcha here. If the user types
abcde
then theread
completes without them needing to press RETURN. This limits the results to 5 characters, but may not be a good user experience. People are used to pressing RETURN.The second method is just to test the length of the input and complain if it's too long. We use the fact that
${#id}
is the length of the string.This results in a pretty standard loop.
If you want it to be exactly 5 characters then you can change the
if
test from-gt
to-eq
.