while IFS= read -r line || [[ $line ]]; do ...; done < /path/to/input
The one-liner is a robust way to write this simpler naive one-liner:
while read line; do ...; done < /path/to/input
For each line in /path/to/input
, store the content of the line in the variable line
, and do something with it.
What can go wrong? Why are all the extra things necessary in the one-liner to make the line by line text processing robust?
Without the IFS=
prefix (setting IFS
to empty string for the read
), whitespace at the beginning and at the end of lines will not be stored in line
. Take for example this input:
hello
^^ ^^ spaces!
Without the IFS=
prefix, the value of line
would be "hello"
instead of " hello "
. If you want to preserve these spaces, make sure to use the IFS=
prefix.
Without the -r
flag, read
would interpret backslash \
characters as escape symbols. Take for example this input:
first line\
second line
Without the -r
flag, the trailing \
at the end of the first line would be interpreted to escape the line break and thereby continue the input, the value of line
would become first linesecond line
.
Without the || [[ $line ]]
, if the last line of the input doesn't have an EOL (end-of-line) character, the loop body will not be reached. This is because read
exits with failure when it reaches EOF (end-of-file), which causes the loop to end.
The || [[ $line ]]
takes care of this corner case:
||
fails, its right side is executed.[[ $line ]]
checks if $line
actually has some content.$line
will be empty, and we want to stop the loop.$line
will not be empty, and we want to continue the loop. In the following iteration the loop will really end, because we're still at the end of the file, and $line
will be empty then.To read the lines in the input exactly as they are, the full one-liner idiom at the top is needed.