Using "while read..." in a linux script
Could somebody please explain how the following code works?
echo '1 2 3 4 5 6' | while read a b c
do
echo $c $b $a
done
Specifically, I'd like to know why the output of this loop is 3 4 5 6 2 1
, instead of 3 2 1
and 6 5 4
on two separate lines? I can't seem to wrap my mind around it...
read
reads a whole line from standard input, splits the line into fields and assigns this fields to the given variables. If there are more pieces than variables, the remaining pieces are assigned to the last variable.
In your case $a
is assigned 1
, $b
is assigned 2
and $c
the remaining 3 4 5 6
.
Rewriting the loop this way reveals what is happening:
echo '1 2 3 4 5 6' | while read a b c
do
echo '(iteration beginning)' a="$a" b="$b" c="$c" '(iteration ending)'
done
This gives, as its output:
(iteration beginning) a=1 b=2 c=3 4 5 6 (iteration ending)
Notice first that only a single echo command is run. If it were run more than once, you would, among other things, see the (iteration beginning)
and (iteration ending)
substrings printed more than once.
This is to say that having a while
loop here is not really accomplishing anything. The read
builtin reads whitespace-separated text1 into each specified variable. Extra input gets appended to the end of the last variable specified.2 Thus variables a
and b
take on the values 1
and 2
respectively, while c
takes on the value 3 4 5 6
.
When the loop condition (while read a b c
) is evaluated the second time, there's no more input available from the pipe (we only piped it a single line of text), so the read
command evaluates to false instead of true and the loop stops (before ever executing the body a second time).
1: To be technical and specific, the read
builtin, when passed variable names as arguments, reads input, splitting it into separate "words" when it encounters IFS whitespace (see also this question and this article).
2: read
's behavior of jamming any extra fields of input into the last variable specified is nonintuitive to many scripters, at first. It becomes easier to understand when you consider that, as Florian Diesch's answer says, read
will always (try to) read a whole line--and that read
is intended to be usable both with and without a loop.