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
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.
Just FYI; in ksh it is working as expected; See also http://kornshell.com/doc/faq.html, Section III (shell programming questions), Q13:
Q13. What is $bar after, echo foo | read bar?
A13. The is foo. ksh runs the last component of a pipeline
in the current process. Some shells run it as a subshell
as if you had invoked it as echo foo | (read bar).
read expects input on a new line
while read a; do echo $a; done foo bar ^D
is more or less what you wanted
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.
The | is an inter-process communications operator. So, implicitly, there is a subprocess that must be created to parse and evaluate the expression on one side of it or the other. Bash, and older Bourne shells create a subprocess to the right of the operator (reading from the pipe) which means that any variables set there are only in scope until that process exits (which is at the semicolon in this code example.
zsh and newer versions of Korn shell (at least from '93 on, but possibly even as far back as ksh '88) will create the subprocess on the other side of the pipe (writing into it). Thus they will work in the way that the author of this question intends. ("As expected" is, of course, highly subjective. Understanding the nature of the pipe should lead one to expect this to behave in an implementation specific manner).
I don't know if there is any specific provision in Posix or Spec 1170 or SuS or any other published standard that requires one or the other semantics. However, in practice it's clear that you must not depend on the behavior; though you can easily test for it within your scripts.
I don't think this one has already been given:
a=`echo foo`
echo $a