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.
There is no syntax like [x..y]
. You're probably thinking of {x..y}
, but that doesn't work if either x
or y
are variables.
So, you could try one of these instead:
for((i=1;i<=$rows;i++)); do
echo "$RANDOM"
done
Or
for i in $(seq 1 "$rows"); do
echo "$RANDOM"
done
Or
i=0
while (( ++i <= rows)); do
echo "$RANDOM";
done
Or even
i=0
until [ $i -gt $rows ]; do
echo "$RANDOM"
((i++))
done
Best Answer
It's because the part where you use the vars is a new set of commands. Use this instead:
Note that, in this syntax, there must be a space after the
{
and a;
(semicolon) before the}
. Also-n1
is not necessary;read
only reads the first line.For better understanding, this may help you; it does the same as above:
Edit:
It's often said that the next two statements do the same:
Well, not exactly. The first one is a pipe from
head
tobash
'sread
builtin. One process's stdout to another process's stdin.The second statement is redirection and process substitution. It is handled by
bash
itself. It creates a FIFO (named pipe,<(...)
) thathead
's output is connected to, and redirects (<
) it to theread
process.So far these seem equivalent. But when working with variables it can matter. In the first one the variables are not set after executing. In the second one they are available in the current environment.
Every shell has another behavior in this situation. See that link for which they are. In
bash
you can work around that behavior with command grouping{}
, process substitution (< <()
) or Here strings (<<<
).