Nick Bull
Nick Bull

Reputation: 9846

Capturing pipe output and the side effects of subshells

The code

In the example function a, I capture the input from a pipe as follows:

function a() {
    if [ -t 1 ]; then 
        read test
        echo "$test"
    fi
    # If $1 has a value, print it
    if [[ -n "$1" ]]; then 
        echo "$1"
    fi
}

called as follows:

echo "hey " | a "hello"

produces the output:

hey
hello

The issue

I was inspired by this answer, however a quote after the snippet has me concerned:

But there's no point - your variable assignments may not last! A pipeline may spawn a subshell, where the environment is inherited by value, not by reference. This is why read doesn't bother with input from a pipe - it's undefined.

I'm not sure I understand this - attempting to create subshells yielded the output I expected:

function a() { 
    (
        if [ -t 1 ]; then 
            read test
            echo "$test"
        fi

        if [[ -n "$1" ]]; then 
            echo "$1"
        fi
    )
}

And in the method call:

(echo "hey") | (a "hello")

still yields:

hey
hello

So what is meant by your variable assignments may not last! A pipeline may spawn a subshell, where the environment is inherited by value, not by reference.? Is there something that I've misunderstood?

Upvotes: 1

Views: 359

Answers (2)

rici
rici

Reputation: 241671

The quoted note is incorrect. read doesn't care where its input comes from.

However, you must remember that the variable assigned to by the invocation of the read command is part of the (sub-)shell which executes the command.

By default, each command executed in a pipeline (a series of commands separated by |) is executed in a separate subshell. So after you execute echo foo | read foo, you will find that the value of $foo has not changed: not because read ignored its input but rather because the shell read executed in no longer exists.

Upvotes: 2

redneb
redneb

Reputation: 23840

Try this:

echo test | read myvar
echo $myvar

You might expect that it will print test, but it doesn't, it prints nothing. The reason is that bash will execute the read myvar in a subshell process. The variable will be read, but only in that subshell. So in the original shell the variable will never be set.

On the other hand, if you do this:

echo test | { read myvar; echo $myvar; }

or this

echo test | (read myvar; echo $myvar)

you will get the expected output. This is what happens with your code.

Upvotes: 1

Related Questions