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.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →