I know a redirection is all I need, just never got to remember how exactly to do that even it’s dead simple. Therefore, this post is written and I hope will never need to read this post, because that means I would’ve remembered how to do it.

1   The loop which doesn’t work

From time to time, there is a script that needs read both user input and lines from a file, it all begins with:

while read line; do
  echo "LINE : $line"
  read -p "INPUT> "
  echo "REPLY: $REPLY"
done <FILE

Note

read reads into $REPLY by default, if you don’t supply a variable name.

With an input FILE that is three lines of numbers:

for i in {01..03}; do echo $i; done >FILE

That will never work as you wanted, because both reads read from the same file, since within that loop, the standard input, &0, is no longer from your keyboard, but the <FILE. You will get this output:

LINE : 01
REPLY: 02
LINE : 03
REPLY:

You can see the read within loop also reads from the FILE. You will expect to have an output and inputs like:

LINE : 01
INPUT> a
REPLY: a
LINE : 02
INPUT> b
REPLY: b
LINE : 03
INPUT> c
REPLY: c

2   Redirecting FILE

while read -u 3 line; do
  echo "LINE : $line"
  read -p "INPUT> "
  echo "REPLY: $REPLY"
done 3<FILE

The file read reads from file descriptor 3 which is a redirection from the FILE using 3<FILE. Since &0 is untouched, the read in the loop can still read from keyboard without problems.

3   Opening FILE as file descriptor

You can also open the FILE as &3, this can be used throughout the script, if somehow cross loop reading is needed:

exec 3<FILE
while read -u 3 line; do
  echo "LINE : $line"
  read -p "INPUT> "
  echo "REPLY: $REPLY"
done
exec 3<&-

The last exec closes the file.

4   Duplicating the stdin

exec 3<&0
while read line; do
  echo "LINE : $line"
  read -p "INPUT> " -u 3
  echo "REPLY: $REPLY"
done <FILE
exec 3<&-

The file read reads like any other file reading loop, but the read inside, it reads from &3, which is a duplicate of standard input from outside of loop, that is the keyboard input we want.

This means, when in outside of the loop, after exec, both &0 and &3 are from keyboard, but as entering the loop, the &0 will be from the file because of <FILE. However, once the loop ends, the &0 will be restored and you can safely read from keyboard through it again.

The last line closes &3 to end the duplication of &0.