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.