Unix & Linux
bash shell-script read
Updated Tue, 02 Aug 2022 22:25:38 GMT

Use read as a prompt inside a while loop driven by read?


I have a use case where I need to read in multiple variables at the start of each iteration and read in an input from the user into the loop.

Possible paths to solution which I do not know how to explore --

  1. For assignment use another filehandle instead of stdin
  2. Use a for loop instead of ... | while read ... ... I do not know how to assign multiple variables inside a for loop

    echo -e "1 2 3\n4 5 6" |\
    while read a b c; 
    do 
      echo "$a -> $b -> $c";
      echo "Enter a number:";
      read d ;
      echo "This number is $d" ; 
    done
    



Solution

If I got this right, I think you want to basically loop over lists of values, and then read another within the loop.

Here's a few options, 1 and 2 are probably the sanest.

1. Emulate arrays with strings

Having 2D arrays would be nice, but not really possible in Bash. If your values don't have whitespace, one workaround to approximate that is to stick each set of three numbers into a string, and split the strings inside the loop:

for x in "1 2 3" "4 5 6"; do 
  read a b c <<< "$x"; 
  read -p "Enter a number: " d
  echo "$a - $b - $c - $d ";
done

Of course you could use some other separator too, e.g. for x in 1:2:3 ... and IFS=: read a b c <<< "$x".


2. Replace the pipe with another redirection to free stdin

Another possibility is to have the read a b c read from another fd and direct the input to that (this should work in a standard shell):

while read a b c <&3; do
    printf "Enter a number: "
    read d
    echo "$a - $b - $c - $d ";
done 3<<EOF
1 2 3
4 5 6
EOF

And here you can also use a process substitution if you want to get the data from a command: while read a b c <&3; ...done 3< <(echo $'1 2 3\n4 5 6') (process substitution is a bash/ksh/zsh feature)


3. Take user input from stderr instead

Or, the other way around, using a pipe like in your example, but have the user input read from stderr (fd 2) instead of stdin where the pipe comes from:

echo $'1 2 3\n4 5 6' |
while read a b c; do 
    read -u 2 -p "Enter a number: " d
    echo "$a - $b - $c - $d ";
done

Reading from stderr is a bit odd, but actually often works in an interactive session. (You could also explicitly open /dev/tty, assuming you want to actually bypass any redirections, that's what stuff like less uses to get the user's input even when the data is piped to it.)

Though using stderr like that might not work in all cases, and if you're using some external command instead of read, you'd at least need to add a bunch of redirections to the command.

Also, see Why is my variable local in one 'while read' loop, but not in another seemingly similar loop? for some issues regarding ... |while.


4. Slice parts of an array as needed

I suppose you could also approximate a 2D-ish array by copying slices of a regular one-dimensional one:

data=(1 2 3 
      4 5 6)
n=3
for ((i=0; i < "${#data[@]}"; i += n)); do
    a=( "${data[@]:i:n}" )
    read -p "Enter a number: " d
    echo "${a[0]} - ${a[1]} - ${a[2]} - $d "
done

You could also assign ${a[0]} etc. to a, b etc if you want names for the variables, but Zsh would do that much more nicely.





Comments (5)

  • +1 – that was great! And what a great overview of the different workarounds! — Aug 03, 2018 at 09:56  
  • +0 – @StéphaneChazelas, ah yes, you're right. Using stderr like that is a bit icky, but I had the recollection that some utility does it. But all I can find now is ones that just use /dev/tty. Oh well. — Aug 03, 2018 at 12:20  
  • +0 – Note that using <&2 (as well as </dev/tty) avoid reading from the stdin of the script. This will not work printf '682\n739' | ./script. Also note that read -p only work in bash. — Aug 04, 2018 at 04:54  
  • +0 – @Isaac, in the one with the read from stderr, there's also the pipe from the echo to that while loop, so you can't really use the script's stdin anyway... read -u is also bash, but can be replaced with redirections, and <<< in the first one is also nonstandard, but that's a bit harder to work around. — Aug 04, 2018 at 09:08  
  • +0 – All could be solved, please: read my answer — Aug 04, 2018 at 09:12  


External Links

External links referenced by this document: