Bash variable scope and pipelines
I alluded to this nuance involving variable scope in my post on automating pdf processing, but I wanted to expand on it a bit.
Consider this little snippet:
i=0
printf "foo:bar:baz:quux" | grep -o '[^:]\+' | while read -r line ; do
printf "Inner scope: %d - %s\n" $i $line
((i++))
[ $i -eq 3 ] && break;
done
printf "====\nOuter scope\ni = %d\n" $i;If you run this script - not in interactive mode in the shell - but as a script, what will i be in the outer scope? And why?
Unless you look carefully, you would think that i should be 3. After all, the while loop exits on a test for equality of i and 3, right? But no, i remains 0 in the outer scope; and this is because each command in a pipeline runs in its own subshell. From the GNU bash manual section on pipelines:
Each command in a pipeline is executed in its own subshell, which is a separate process. (Emphasis is mine.)
The last command in the pipeline in the above example is the while loop, so it’s merrily executing in its own subshell modifying its own i while the outer scope is unaware of what’s happening in this shell. This explains why i remains 0 in the outer scope.
But what are we to do if we want the inner scope to modify i? The key is to set the lastpipe option with shopt -s lastpipe. This option introduced in bash 4.2 forces the last command in the pipeline to run in the outer shell environment. So now if we modify the script with this option:
shopt -s lastpipe
i=0
printf "foo:bar:baz:quux" | grep -o '[^:]\+' | while read -r line ; do
printf "Inner scope: %d - %s\n" $i $line
((i++))
[ $i -eq 3 ] && break;
done
printf "====\nOuter scope\ni = %d\n" $i;what is i in the outer scope? Right, it’s 3 this time because the while loop is executing in the shell environment, not in its own subshell.