Line-oriented scripts need to keep the text exactly as it arrives. IFS= read -r is the usual safe loop shape.

Program

Play the script to watch two input lines update the loop variables without losing the embedded space.

read_lines.sh
Replay: real traced execution (multi-file project)
#!/usr/bin/env bash

count=0
last=""
while IFS= read -r line; do
  count=$((count + 1))
  last="$line"
done <<'DATA'
alpha
two words
DATA
echo "$count:$last"
  1. count ← 0

    3count=04last=""
    values this step0count
  2. last ← (empty)

    3count=04last=""5while IFS= read -r line; do
    values this step(empty)last
  3. line ← alpha

    4last=""5while IFS= read -r line; do6  count=$((count + 1))
    values this stepalphaline
  4. count ← 1

    5while IFS= read -r line; do6  count=$((count + 1))7  last="$line"
    values this step0 1count
  5. last ← alpha

    6  count=$((count + 1))7  last="$line"8done <<'DATA'
    values this stepalphalastalphaline
  6. line ← two words

    4last=""5while IFS= read -r line; do6  count=$((count + 1))
    values this steptwo wordsline
  7. count ← 2

    5while IFS= read -r line; do6  count=$((count + 1))7  last="$line"
    values this step1 2count
  8. last ← two words

    6  count=$((count + 1))7  last="$line"8done <<'DATA'
    values this steptwo wordslasttwo wordsline
  9. echo "$count:$last"

    11DATA12echo "$count:$last"
    output2:two words
    values this step2counttwo wordslast

Follow the Lines

  1. count starts at 0, and last starts empty.
  2. The first input line is alpha, so count=1 and last=alpha.
  3. The second input line is two words.
  4. The loop keeps the embedded space, so last="two words".
  5. The script prints 2:two words. | input line | count after read | last after read | | --- | --- | --- | | alpha | 1 | alpha | | two words | 2 | two words | | stdout | 2 | two words |
read -r `read -r` reads a line without treating backslashes as escape characters.
IFS= `IFS=` keeps leading and trailing whitespace from being trimmed before assignment.
here document `<<'DATA'` feeds literal lines to the loop until the closing marker.

Exercise: read_lines.sh

Reproduce 2:two words, then trace which line becomes the final last value.