36

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

43

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.

  • 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 4 '15 at 2:17
24

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, 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 4 '15 at 2: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 4 '15 at 3:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.