Skip to content
TaeyoungKim.dev

Bash while read Skips the Last Line: Preserve It with IFS= and -r

LinuxWritten 3 min readTaeyoungKim
LinkedInX

You write a script to read a configuration list line by line. The file visibly has two lines, but the last item is never processed. If the final line has no newline, read can put its text in a variable yet return a nonzero status at end of file. A while condition that checks only that status discards the text.

What happens without a final newline?

The basic loop may trim surrounding spaces and skip the newline-free last line. IFS= read -r preserves the input, while an additional condition processes any content still held in line.

First feed two lines without creating a file. The printf input has no \n after last:

bash
printf '  first  \nlast' | while read line; do
  printf '<%s>\n' "$line"
done
text
<first>

The spaces around the first line disappear, and the last line is missing. Default IFS handling accounts for the spaces; the final read reaches end of file without a newline and reports a nonzero status. Seeing both effects at once can make it look as if the input was truncated.

Combine IFS= read -r with a remaining-content check

IFS= preserves leading and trailing spaces, while -r prevents read from interpreting backslashes as escapes. The extra condition processes one last line when read reaches end of file after placing nonempty text in line.

bash
printf '  first  \nlast' | while IFS= read -r line || [ -n "$line" ]; do
  printf '<%s>\n' "$line"
done
text
<  first  >
<last>

[ -n "$line" ] asks whether nonempty content remains. When the input ends with a newline, read has already processed each line and the final empty variable ends the loop. Without that newline, the remaining text gets one more pass. Also test empty input and newline-terminated input to make sure the loop does not duplicate a line.

When input contains backslashes

The -r option is especially important when text can contain \. Plain read treats a backslash specially. Feed printf '%s\n' 'a\b' through each loop and compare the printed characters to see the difference.

Why does a variable outside the pipe stay unchanged?

The examples use printf ... | while ... to show output. In Bash, the while side of a pipeline may run in a separate shell environment, so do not assume a counter changed inside the loop will retain its value afterward. To read a real file and keep the counter in the current shell, redirect the file into the loop:

bash
count=0
while IFS= read -r line || [ -n "$line" ]; do
  count=$((count + 1))
done < items.txt
printf 'processed=%s\n' "$count"

This is a line-oriented plain-text pattern. IFS alone cannot parse real CSV with embedded newlines or commas in quoted fields; use a CSV parser for that. Replace items.txt with a file you intend to read, and test a small sample before applying the loop broadly.

Key takeaways

If a while read loop skips the last line, check whether that line has a newline. IFS= read -r line || [ -n "$line" ] preserves surrounding spaces, backslashes, and a nonempty final line without a newline. When reading through a pipe, also check whether the loop's shell environment affects state you need afterward.

Author

TaeyoungKim

Connecting technical foundations with implementation, verification, and production decisions.

#Bash#while read#IFS#file input#final line

Read next