I set variables in a loop. Why do they disappear after the loop terminates? Or, why can't I pipe data to read?

The problem

Each command of a pipeline of at least two commands - where "command" can be any of: a simple or compound command, or pipeline - is executed asynchronously in a subshell. Or more simply, in most shells, each chunk of code separated by a pipe operator, including compound commands (which includes while/for/until loops) are forked off and executed at the same time in separate SubShell processes, which like all subshells, each have their own isolated environment and variable scope.

Non-working example:

    # Works only in ksh88/ksh93
    typeset -i linecnt=0
    printf '%s\n' foo bar | while read -r line

    do
        linecnt=$((linecnt+1))
    done
    printf 'total number of lines: %s\n' "$linecnt" # prints 0

The reason for this potentially surprising behaviour, as described above, is that each SubShell introduces a new variable context and environment. The while loop above is executed in a new subshell with its own copy of the variable linecnt created with the initial value of '0' taken from the parent shell. This copy then is used for counting. When the while loop is finished, the subshell copy is discarded, and the original variable linecnt of the parent (whose value hasn't changed) is used in the echo command.

Different shells exhibit different behaviors in this situation:

More broken stuff:

    # Bash 4
    # The problem also occurs without a loop
    printf '%s\n' foo bar | mapfile -t line  
    printf 'total number of lines: %s\n' "${#line[@]}" # prints 0

    f() {
        if [[ -t 0 ]]; then
            echo "$1"
        else
            read -r var
        fi
    };

    f 'hello' | f
    echo "$var" # prints nothing

Again, in both cases the pipeline causes read or some containing command to run in a subshell, so its effect is never witnessed in the parent process.

Workarounds

For more related examples of how to read input and break it into words, see FAQ #1.


CategoryShell