POSIX – Why Last Function in Shell Script Pipeline Doesn’t Retain Variable Values

posixshell-script

Assume the following script:

#!/bin/sh

func1() {
  eval $1'=$(cat)'
  eval echo "Value$2 inside function : \$$1"
}

func1 x 1 <<'HEREDOC'
Hello World
HEREDOC

echo "Value1 outside function: $x"

x=""

echo "Hello World" | func1 x 2

echo "Value2 outside function: $x"

On bash 4.3.43-4.fc25, the output is:

Value1 inside function : Hello World
Value1 outside function: Hello World
Value2 inside function : Hello World
Value2 outside function: 

Using the bashism shopt -s lastpipe makes the last line also show "Hello World," but intuitively I don't understand why that doesn't happen automatically. Is this expected?

It appears the POSIX standard doesn't really discuss this topic: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_02

Best Answer

POSIX says that:

each command of a multi-command pipeline is in a subshell environment

and that

Changes made to the subshell environment shall not affect the shell environment

If you think about the most straightforward way of implementing a pipeline, this makes sense - call pipe(), fork a subshell, and swap out standard output/input on one side each, then execute the command in the subshell. This is also the defined behaviour of Bash.

You can't rely on that happening, though, because

as an extension, however, any or all commands in a pipeline may be executed in the current environment

That's what lastpipe does for that case. In some other shells, that's the default behaviour in certain situations.