Skip to content
Latchkey

bash read-while loop in a pipe loses variables in CI

A pipeline runs each stage in its own subshell. So cmd | while read line; do count=$((count+1)); done increments count inside a subshell, and after the loop the parent shell still sees the old value. The loop worked; its variable changes just did not survive.

What this error means

A counter, flag, or accumulated value is set inside a while read loop but is empty or zero after the loop. The loop body clearly ran (you can echo inside it), yet nothing persists.

bash
count=0
grep ERROR log.txt | while read -r line; do
  count=$((count + 1))
done
echo "$count"   # prints 0: the loop ran in a subshell

Common causes

The loop is the last stage of a pipeline

In bash (default), each pipeline command runs in a subshell. Variables assigned in the loop live and die in that subshell.

Relying on side effects across the pipe boundary

Any state the loop mutates - counters, arrays, flags - is discarded when the subshell exits.

How to fix it

Feed the loop with a redirect or process substitution

Avoid the pipe so the loop runs in the current shell. A here-string or process substitution keeps the loop in the parent.

bash
count=0
while read -r line; do
  count=$((count + 1))
done < <(grep ERROR log.txt)
echo "$count"   # correct

Or enable lastpipe in bash

With job control off, shopt -s lastpipe runs the last pipeline stage in the current shell so its variables persist.

bash
shopt -s lastpipe
grep ERROR log.txt | while read -r l; do count=$((count+1)); done
echo "$count"

How to prevent it

  • Feed while read from < file or < <(cmd), not a pipe.
  • Keep any needed state outside the piped subshell.
  • Use shopt -s lastpipe when a pipe into a loop is unavoidable.

Frequently asked questions

What causes "while read in a pipe runs in a subshell"?
In bash (default), each pipeline command runs in a subshell. Variables assigned in the loop live and die in that subshell.
How do I fix while read in a pipe runs in a subshell?
Avoid the pipe so the loop runs in the current shell. A here-string or process substitution keeps the loop in the parent.

Related guides

References

Latchkey auto-heals failures like this one - detected, fixed, and retried without you. Start free → 30-day trial · No credit card