Bash: while statement with ‘read’ not processing last value

An often unexpected behavior of the ‘read‘ utility within a ‘while’ statement is that the last value will not be processed if it does not end with the delimiter.

For example, if you are using while to read multiple lines and the last line ends with just EOF (instead of a linefeed followed by EOF), the last line will not be processed.

Similarly, if you are parsing a single line with character delimiter (such as a comma separated list), the last value will not be processed unless there is a comma at the very end of the line.

# multiple lines, does not end with newline
multiple_lines="first line\nmiddle line\nlast line"

# use '-en' to make sure echo does not add newline
while read -r line; do \
  echo "my line: $line" \
done < <(echo -en "$multiple_lines")

# notice that last line is missing!
my line: first line
my line: middle line

For this contrived example we could append a newline to the input string or we could have echo add the newline.  But we can’t always control the input, and it would be better if there was a solution that worked regardless of the  last character.  By adding a test for non-zero length of the variable, this can be achieved:

while read -r line || [ -n "$line" ]; do
  echo "my line: $line"
done < <(echo -en "$multiple_lines")

# the last line is now processed
my line: first line
my line: middle line
my line: last line

In the same manner, for a single line being parsed using character delimiters (such as csv), unless the string ends with comma delimiter, the last value is not processed.  This can be remedied by adding the same string length test.

mycsvlist="a,b,c"
while read -r -d ',' val ; do
  echo "val: $val"
done < <(echo "$mycsvlist")

# notice that last csv value is missing!
val: a
val: b

Again, by adding the non-zero length test, the last value is processed without needing to add a comma at the end of the string.

mycsvlist="a,b,c"
while read -r -d ',' val || [ -n "$val" ] ; do
  echo "val: $val"
done < <(echo "$mycsvlist")

# the last value is now processed
val: a
val: b
val: c

 

REFERENCES

man page for ‘read’

stackoverflow, non-zero test length to make sure last value is processed.

stackoverflow, read last line of script