tags:

views:

98

answers:

3

Code:

while read line;
do
  tr 3 4
done<<EOF
1
2
3
4
EOF


Produces:

2
4
4

Where does the 1 go?

+5  A: 
$ while read line
> do
>     tr 3 4
> done<<EOF
> 1
> 2
> 3
> 4
> EOF
2
4
4

What happens here is that read line gets the first line with the 1. The rest of the input is passed to tr, which replaces 3 with 4, so 2 3 4 becomes 2 4 4

The 1 goes into a variable line. Example

$ while read foo; do echo "This is $foo"; done <<EOF
> madness
> SPARTAAAAAAAAAAAA
> EOF
This is madness
This is SPARTAAAAAAAAAAAA

The strange behavior you see is because read is a builtin, and is somewhat special. When you perform piping, for example, suppose you write

echo "foo bar" | read i

You would expect something in $i, right ? try it, it will be empty. Why? When you do a piping, each command of the pipe runs in a subshell. This is true for everything, and always.

read is kind of strange, because it is not a command, it is a builtin of the shell, and it has to export a variable so that you can see it. If you use the pipe, like in the example above, it will export i in a subprocess (a subshell), and you will never see it because the shell environment you are working in is the parent, and a subshell cannot set the environment of its parent.

In your case, what happens is that read, together with while, as builtins, are acting in your shell. read dutifully does its job of taking the first thing coming from stdin and setting it into the variable of the same shell. This removes the 1 from the stdin, because this is what the read builtin does. The stdin is now left with the remaining stuff, which is passed to tr because in the while, the stdin is intercepted by the first command. Try this

$ while true; do rev; echo "x"; done <<EOF
> hello
> EOF
olleh

you will remain stuck with a pile of x. rev has already completed (check its output in the firsts lines), and the stdin is closed (you did it with EOF). the while loop will continue forever (and you will get a cascade of x) but you cannot provide anything anymore to rev, because you are not typing to the stdin anymore.

Stefano Borini
This was on a past exam for a subject I'm prepping for, and I'm having trouble understanding the output. I don't get why it skips 1.
LDK
I don't understand that too, why 'read line' gets the first line and only the rest goes to tr? Where does the '1' go?
Alberto Zaccagni
it goes in a variable $line
Stefano Borini
Stefano, the above example makes complete sense but I'm still having trouble understanding why $line gets 1 and tr gets the rest? It seems like there is some implicit magic going on.
LDK
There is, I'm working on it.
Stefano Borini
Thanks for the explanation :]
Alberto Zaccagni
Great explanation!
LDK
bash is bastard. should carry a big red sign: use with caution, risk of death (or a lot of pain)
Stefano Borini
+2  A: 

Stefano's answer is very good. Maybe you want to do this?

tr 3 4 <<EOF
1
2
3
4
EOF
0x89
+2  A: 

this is a followup on Stephano's answer - all credit should go to him. here is how you can understand this:

first you asked sh to 'read' a line, so it did. then you asked it do 'tr 3 4'. now, 'read' only reads one line, but 'tr' will read all the input until it was finished, so when you get back to 'read' there is nothing more in stdin and the program finished. since you never told sh to echo $line, you wont get 1 in the output.

the following should print 1 2 4 4

while read line
do
   echo $line | tr 3 4
done
Nir Levy