If there are some characters after the last line in the file (or to put it differently, if the last line is not terminated by a newline character), then read will read it but return false, leaving the broken partial line in the read variable(s).
You can process this after the loop:
# Emulate cat. while IFS= read -r line; do printf '%s\n' "$line" done < "$file" [[ -n $line ]] && printf %s "$line"
or:
# This does not work: printf 'line 1\ntruncated line 2' | while read -r line; do echo $line; done # This does not work either: printf 'line 1\ntruncated line 2' | while read -r line; do echo "$line"; done; [[ $line ]] && echo -n "$line" # This works: printf 'line 1\ntruncated line 2' | { while read -r line; do echo "$line"; done; [[ $line ]] && echo "$line"; }
NOTE:
The first example, beyond missing the after-loop test, is also missing quotes.
Alternatively, you can simply add a logical OR to the while test:
while IFS= read -r line || [[ -n $line ]]; do printf '%s\n' "$line" done < "$file" printf 'line 1\ntruncated line 2' | while read -r line || [[ -n $line ]]; do echo "$line"; done