Search code examples
bashpipe

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?


Solution

  • 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.