37

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...

wjandrea
  • 14,109
  • 4
  • 48
  • 98
linuxgringo
  • 371
  • 1
  • 3
  • 8

2 Answers2

44

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.

guntbert
  • 12,914
  • 37
  • 45
  • 86
Florian Diesch
  • 86,013
  • 17
  • 224
  • 214
  • Thanks Florian! Now it makes sense... For some reason I thought spaces would delimit each variable reading, but apparently not. I appreciate your help!! – linuxgringo Apr 04 '15 at 02:17
25

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.

Eliah Kagan
  • 116,445
  • 54
  • 318
  • 493
  • Eliah, thanks for taking the time to explain all the details. I suspected that `while` didn't serve its normal purpose in this example, but then the `read` command threw me off... Somehow, I interpreted it as "while `read a b c` is not false, do `echo ...`". Thank you for explaining how it really worked. I came across this code yesterday and knew it would bug me until I figured it out... lol – linuxgringo Apr 04 '15 at 02:22
  • @linuxgringo Actually, the loop body *is* executed each time `read a b c` evaluates to true, and the loop condition (`read a b c`) does run more than once. Bit it only evaluates to *true* the 1st time. The 2nd time, there's no more input to be read from the pipe, so *end of file* is encountered, causing `read` to return *false*. (See the last section of the output of `help read`, on "Exit Status," for details, noting that, in shell scripting, zero means true and nonzero means false.) If you piped more than one line of input to `while read ...`, the loop body would get executed multiple times. – Eliah Kagan Apr 04 '15 at 03:05