As said, read first reads a full line from standard input, i.e, a block of (UTF-8) text finished by a newline, and then it assigns what it read to whatever variables it finds on the input.
Note that at least the bash variant of read does use spaces as delimiters (by default), but the line must be read first (and that requires a newline).
echo '1 2 3 4 5 6' obviously only provides one line of input, so the while loop is just run once. The explanation of the 'strange' behaviour of how it then assigns variables has been already mentioned (i.e., the last variable gets 'all the rest of the match').
But, of course, we can cheat.
The first cheat is simply to place '1 2 3'and '4 5 6' in separate lines:
echo '1 2 3
4 5 6' | while read a b c; do
echo $c $b $a;
done
(to type the example above directly on the shell, you will need to use Shift + Enter in most cases)
Or, alternatively, use two echo commands, one for each line, and eval the result as a single stream to feed to the while loop:
echo $(echo '1 2 3'; echo '4 5 6') | while read a b c; do
echo $c $b $a;
done
Too ugly for your tastes (beyond requiring launching a subprocess)? Well, echo has a nice cute flag, -e, which allows introducing backslashed control characters, such as \n, like this:
echo -e '1 2 3\n4 5 6' | while read a b c; do
echo $c $b $a;
done
Alternatively, you can replace echo with printf. printf, as its name implies, prints structured formatting, inspired by the C family of programming languages.
printf '1 2 3\n4 5 6\n' | while read a b c; do
echo $c $b $a;
done
Notice that printf, like it's C-style equivalents, doesn't print a terminating newline on its own — you've got to explicitly add it at the end.
Finally, my favourite way: using a here-doc, like this:
cat | while read a b c; do echo $c $b $a; done << EOF
1 2 3
4 5 6
EOF
For best results, it's easier to keep everything in a single line when typing it out on the shell.
There are more variants to the theme, of course, but these should suffice — for now, at least!
Have fun