The Pickle Tickler The Pickle Tickler - 2 months ago 5
Bash Question

How to alternate between file input and terminal input in bash

I am writing a

script that reads from a file. After reading from the file, I would like to prompt the user for input, and read it from the terminal.

Here's a excerpt of my code:

while IFS=',' read -r a b c
#a, b, c are read in from file

#later in the loop
#answer should be read in from the command line
echo "Enter your answer to continue:"
read answer


However, currently I think the script thinks I am trying to read in
from the same input file as
, and
. How do I alternate between file and terminal input?


If your stdin has been redirected from a file (that is, you were invoked with ./yourscript <file), then use /dev/tty to read from the terminal:


exec 3</dev/tty || {
  echo "Unable to open TTY; this program needs to read from the user" >&2
  exit 1

while IFS= read -r line; do # iterate over lines from stdin
  if [[ $line = Q ]]; then
    echo "Getting input from the user to process $line" >&2
    read -r answer <&3 # read input from descriptor opened to /dev/tty earlier
    echo "Processing $line internally"

If you want to skip the exec 3</dev/tty up by the top (opening /dev/tty just once at the beginning of your script, allowing reads from the TTY to later be done with <&3), then you could instead write:

read -r answer </dev/tty open it every time you want to perform a read from the terminal. However, you'd want to be sure to have error-handling for the case where it fails on these occasions within your loop (for instance, if this code is running from a cron job, a ssh invocation with a command passed as an argument and no -t, or a similar situation where there is no TTY).

Alternately, consider opening your file on a descriptor other than stdin -- here, we use file descriptor #3 for file input, and assume invocation as ./yourscript file (with stdin pointing to the terminal):


while IFS= read -r line <&3; do # reading file contents from FD 3
  if [[ $line = Q ]]; then
    echo "Getting input from the user to process $line" >&2
    read -r answer # reading user input by default from FD 0
    echo "Processing $line internally" >&2
done 3<"$filename" # opening the file on FD 3