2

I'm trying to pipe a command's output to the read builtin of my shell and I get a different behaviour for zsh and bash:

$ bash -c 'echo hello | read test; echo $test'

$ zsh -c 'echo hello | read test; echo $test'
hello

Though that doesn't work in bash, the following works for both:

 $ bash -c 'echo hello | while read test; do echo $test; done'
 hello
 $ zsh -c 'echo hello | while read test; do echo $test; done'
 hello

Why is that? Am I using read wrong? I find it much more readable to use it in scripts in comparison to test="$(echo hello)" which forces me to handle quoting issues much more carefully.

  • see also this. notice that bash could be made to act the same as zsh in this case: bash -c 'shopt -s lastpipe; echo hello | read test; echo $test' –  Oct 21 '18 at 10:33

1 Answers1

4

You are observing the results from what has not been standardized with POSIX.

POSIX does not standarize how the interpreter runs a pipe.

With the historic Bourne Shell, the rightmost program in a pipeline is not even a child of the main shell. The reason for doing it this way is because this implementation is slow but needs few code - important if you only have 64 kB of memory. Since in this variant, the read command is run in a sub process, assigning a shell variable in a sub process is not visible in the main shell.

Modern shells like ksh or bosh (the recent Bourne Shell) create pipes in a way where all processes in a pipe are direct children of the main shell and if the rightmost program is a shell builtin, it is even run by the main shell.

This all is needed to let the read program modify a shell variable of the main shell. So only in this variant (that BTW is the fastest variant) permits the main shell to see the results from the variable assignment.

In your second example, the whole while loop is run in the same sub process and thus allows to print the modified version of the shell variable.

There is curently a request to add support to the POSIX shell to get information on whether any of the pipeline commands has a non-zero exit code. To achieve this, a shell must be implemented in a way where all programs from a pipeline are direct children from the main shell. This is close to what is needed to allow

echo foo | read val; echo $val

to do what is expected, since then only the requirement to run the read in the main shell is missing.

schily
  • 19,173
  • this is the text from the standard that supports this: "Additionally, each command of a multi-command pipeline is in a subshell environment; as an extension, however, any or all commands in a pipeline may be executed in the current environment." –  Oct 21 '18 at 10:31
  • The grammar in this answer twists and turns in a way, that is hard to make sense of. – ctrl-alt-delor Feb 24 '20 at 22:33