1

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

1

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.

3

Yes, POSIX does mention this topic. From here:

A subshell environment shall be created as a duplicate of the shell environment, except that signal traps that are not being ignored shall be set to the default action. Changes made to the subshell environment shall not affect the shell environment. Command substitution, commands that are grouped with parentheses, and asynchronous lists shall be executed in a subshell environment. 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. All other commands shall be executed in the current shell environment.

It depends on shell to chose its default behavior. In:

echo 1 | read x
echo "$x"

only zsh and ksh output 1, other modern Bourne-like shells output empty string.

  • Thanks for the answer, unfortunately I could only accept one of them and I liked Michael's. I upvoted you :) – Kevin Jan 29 '17 at 6:57
1

Historically, the Bourne shell and the Korn shell behaved differently. Since the two sides of a pipe are executed in parallel, in different processes, it isn't possible for variable assignments on both sides to be preserved in the rest of the script. Given unset a; a=b | a=c, the variable a can end up unset, or equal to b, or equal to c; it can't be equal to both b and c.

In the Bourne shell, each side of the pipe operator runs in a subshell (i.e. in a separate shell process), so variable assignments are not preserved. In the Korn shell, the right-hand side of a pipe is executed in the original shell process.

As in many cases where existing behaviors differed, POSIX allows both behaviors. As stated in the section “Shell Execution Environment”:

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. All other commands will be executed in the current shell environment.

Some systems have implemented the last stage of a pipeline in the current environment so that commands such as:

command | read foo

set variable foo in the current environment. This extension is allowed, but not required; therefore, a shell programmer should consider a pipeline to be in a subshell environment, but not depend on it.

In practice, commands other than the last always run in a subshell, except for some optimizations where the behavior is the same unless you look closely at the number of processes that are created. That is, no shell has unset a; a=b | true; echo $a print b.

Most other Bourne-like shells, such as ash, dash, bash, pdksh and mksh, behave like the Bourne shell. Zsh behaves like the Korn shell. Recent versions of bash can switch to the ksh behavior with shopt -s lastpipe.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.