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