Skip to content
while read — line-based iteration
step 1/7

Reading — step 1 of 7

Learn

~2 min readI/O and Iteration

For processing files line by line, while read is the standard bash idiom. Far safer than for f in $(cat file) (which word-splits and is broken on spaces in names).

The classic pattern

while read -r line; do
    echo "got: $line"
done < file.txt
  • -r — don't interpret backslashes (always use it)
  • < file.txt — redirect file as stdin to the loop
  • read returns non-zero at EOF, terminating the loop

Reading from a command

ls *.txt | while read -r f; do
    echo "$f"
done

Caveat: pipelines run in subshells. Variables set inside the loop are LOST when the loop ends:

count=0
ls | while read -r f; do (( count++ )); done
echo "$count"        # 0! the increment was in a subshell

Fix with process substitution:

count=0
while read -r f; do (( count++ )); done < <(ls)
echo "$count"        # actual count

< <(...) makes the loop run in the parent shell. THIS PATTERN MATTERS — keep it in your toolkit.

Reading multiple fields

With CSV-like input, read splits by IFS:

echo "alice,30,engineer" | while IFS=',' read -r name age role; do
    echo "$name is $age, works as $role"
done

But multiple lines:

while IFS=',' read -r name age role; do
    echo "$name: $age, $role"
done < users.csv

IFS=',' is set ONLY for that read command — doesn't affect the rest of the script.

Reading the last incomplete line

If the last line lacks a trailing newline, default read skips it:

printf "a\nb\nc" | while read -r line; do echo "got: $line"; done
# only prints "got: a" and "got: b" — c is dropped

printf "a\nb\nc" | while read -r line || [[ -n "$line" ]]; do echo "got: $line"; done
# now prints all three

The || [[ -n "$line" ]] catches the partial last line.

Reading from stdin (for scripts)

while read -r line; do
    echo "received: $line"
done

Reads until EOF. Useful for scripts that process piped input: cat data | ./process.sh.

When NOT to use while read

For file processing, awk is often better:

awk -F',' '{ print $1, $2 }' users.csv

For simple grepping, grep is faster than reading line-by-line in bash. Use while read when you need bash logic per line that's hard to express in awk/grep.

Common mistakes

  • Forgetting -r — backslashes get interpreted as escapes. Always use -r.
  • Pipeline subshell variable loss — modifications inside a piped while are invisible outside. Use process substitution.
  • Skipping the last line without newline — add || [[ -n "$line" ]] for safety.
  • for line in $(cat file) — word-splits on whitespace, breaks on multi-word lines. Always use while read.
  • Read with mistuned IFS — set IFS in the same command (while IFS=',' read ...) not globally.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…