I could replicate the problem with various shells under FreeBSD, GNU/Linux, and Solaris. It had me head-scratching for more than an hour, so I decided to post the question here.
Due to the piping the read
is executed in its own subshell.
echo foo | while read a; do echo $a; done
will do what you expect it to.
alternative:
echo foo | (read a ; echo $a)
Edit:
If you need $a outside the subshell, you have to reverse the commands:
read a < <(echo foo); echo $a
this way the read is executed in the current process
read expects input on a new line
while read a; do echo $a; done foo bar ^D
is more or less what you wanted
According to Kernighan and Pike's The Unix Programming Environment (p. 159) "none of the shell built-in commands (as opposed to the control flow primitives, like for) can be redirected with > and <", and "this might be described as a bug in the shell". This seems to explain a) why code like
ls *.c |
while read file
do
echo $a
done
invariably works without a problem, and b) the inconsistency that redirecting from a file works.
I don't think this one has already been given:
a=`echo foo`
echo $a
I came up with a solution that doesn't hide the variable values in a subshell, and that can also work with multiple values.
set `echo foo bar`
A=$1
B=$2
Just FYI; in ksh it is working as expected; See also http://kornshell.com/doc/faq.html, Q13