Bash Avoiding Subshells
A hidden subshell is a copy of the shell created by a pipeline, command substitution, or background job. Changes made inside that copy never reach the parent shell.
A pipeline loop loses its count; a redirected loop keeps it
The same file feeds both loops. The pipeline loop ends at 0, while the redirected loop ends at 3.
printf 'one\ntwo\nthree\n' > data.txt
count=0
cat data.txt | while IFS= read -r line; do count=$((count+1)); done
echo "pipeline count: $count"
count=0
while IFS= read -r line; do count=$((count+1)); done < data.txt
echo "redirect count: $count"Command substitution loses a function's assignment
The captured call prints inside: 5, yet the parent total stays 0. A direct call sets the total to 5.
total=0
add() { total=$((total + 5)); echo "inside: $total"; }
result=$(add)
echo "captured: $result"
echo "after substitution total: $total"
add
echo "after direct call total: $total"A background job loses its assignment too
The background job sets its own copy to 7. The parent still sees 0 after wait, which proves the assignment never came back.
value=0
( value=7; echo "inside background: $value" ) &
wait
echo "after background value: $value"Example
shopt -s lastpipe
printf 'red\nblue\ngreen\n' > data.txt
count=0
cat data.txt | while IFS= read -r line; do count=$((count+1)); done
echo "count: $count"
echo "double: $((count * 2))"Key points
- Pipeline trap: every stage of a pipeline runs in a subshell, so loop variables set there are lost.
- Substitution trap: a function called inside $( ) runs in a subshell, so its assignments are lost.
- Background trap: a background job runs in a subshell, so its assignments are lost too.
- Redirect fix: feed a loop by redirection from a file, and the loop runs in the current shell.
- Process substitution fix: reading from <( ) keeps the loop itself in the current shell.
- lastpipe fix: with shopt -s lastpipe, the last pipeline stage runs in the current shell.
- wait: the command that pauses the script until background jobs finish.
In this example, lastpipe is turned on first. The loop runs as the last pipeline stage, so it runs in the current shell. The count survives the pipeline, and the script doubles it after the loop.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
A loop after a pipe sets count to 3, but the parent prints 0. What happened?