While trying the cat $@ trick to read from either standard input or given files, it turned out that pipe and process substitution handle a missing trailing newline differently:printf %s foo > test.txtunset REPLYwhile readdo    :done < <(cat test.txt)echo $REPLY # Prints foounset REPLYcat test.txt | while readdo    :doneecho $REPLY # Prints nothing!Is this by design? Is this feature documented anywhere?D'oh! @fered had the right idea - It's just another example of how variable values are lost because piped commands are run in a subshell.
Pipes vs process substitution
bash;pipe;process substitution
Variables in a pipe never make it out of the pipe alive :)Process substitution redirects the data to a file descriptor. Behind the scenes, that process is not the same as a | pipe.  The following works, because it is all within the same pipe.  unset REPLYcat test.txt | {   while read ;do : ;done  echo $REPLY } # Prints foo!