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.