5

I found some code for reading input from a file a while ago, I believe from Stack Exchange, that I was able to adapt for my needs:

while read -r line || [[ -n "$line" ]]; do
    if [[ $line != "" ]]
    then
        ((x++));
        echo "$x:  $line"
    <then do something with $line>
    fi
done < "$1"

I'm reviewing my script now & trying to understand what it's doing ... I don't understand what this statement is doing:

while read -r line || [[ -n "$line" ]];

I understand that the -r option says that we're reading raw text into line, but I'm confused about the || [[ -n "$line" ]] portion of the statement. Can someone please explain what that is doing?

4

[[ -n "$line" ]] tests if $line (the variable just read by read) is not empty. It's useful since read returns a success if and only if it sees a newline character before the end-of-file. If the input contains a line fragment without a newline in the end, this test will catch that, and the loop will process that final incomplete line, too. Without the extra test, such an incomplete line would be read into $line, but ignored by the loop.

The cmd1 || cmd2 construct is of course just like the equivalent in C. The second command runs if the first returns a falsy status, and the result is the exit status of the last command that executed.

Compare:

$ printf 'foo\nbar' | ( while read line; do echo "in loop: $line" ; done ; echo "finally: $line" )
in loop: foo
finally: bar

and

$ printf 'foo\nbar' | ( while read line || [[ -n $line ]]; do echo "in loop: $line" ; done ; echo "finally: $line" )
in loop: foo
in loop: bar
finally: 
  • A missing LF at EOF totally slipped my mind. Good catch on that... – brhfl Oct 30 '18 at 20:00
  • Thank you both for your answers ... that clears it up. – K. Hilbert Oct 30 '18 at 20:10
1

It's a bit confusing as to why that would be there, but straightforward to explain what it does: || is an OR statement, and [[ -n returns true (success) as long as "$line" has a nonzero length. Here's what's confusing: while loops keep going while there's a successful (0) exit status. read continues to read lines and return a 0 exit status until it hits end of file -- even if those lines are blank. [[ -n "$line" ]] is only going to execute when read returns a nonzero exit code, at which point $line will be empty. Since the test returns true if $line is not empty, we're back to a nonzero exit, throwing us out of the while loop. As far as I can see, || [[ -n "$line" ]] doesn't actually accomplish anything. (As @ilkkachu pointed out, this will catch the odd final line of input that is missing its trailing newline. Note that such a file is not a valid text file, as that line is not a valid line)

Something that is occasionally useful is to do while read -r line && [[ -n "$line" ]]. Using && (AND) means that the whole statement will only return a zero status if read is able to read a line, AND that line is not empty. It will cause the while loop to halt at the first empty line. If I had to make a guess, this snippet of code may have been adapted from one which did just that - and instead of simply removing the test, the author changed the && to ||.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.