It sounds like your input file is missing the newline character after its last line. When read
encounters this, it sets the variable ($line
in this case) to the last line, but then returns an end-of-file error so the loop doesn't execute for that last line. To work around this, you can make the loop execute if read
succeeds or it read anything into $line
:
...
while read line || [[ -n "$line" ]]; do
...
EDIT: the ||
in the while condition is what's known as a short-circuit boolean -- it tries the first command (read line
), and if that succeeds it skips the second ([[ -n "$line" ]]
) and goes through the loop (basically, as long as the read
succeeds, it runs just like your original script). If the read
fails, it checks the second command ([[ -n "$line" ]]
) -- if read
read anything into $line
before hitting the end of file (i.e. if there was an unterminated last line in the file), this'll succeed, so the while
condition as a whole is considered to have succeeded, and the loop runs one more time.
After that last unterminated line is processed, it'll run the test again. This time, the read
will fail (it's still at the end of file), and since read
didn't read anything into $line
this time the [[ -n "$line" ]]
test will also fail, so the while
condition as a whole fails and the loop terminates.
EDIT2: The [[ ]]
is a bash conditional expression -- it's not a regular command, but it can be used in place of one. Its primary purpose is to succeed or fail, based on the condition inside it. In this case, the -n
test means succeed if the operand ("$line"
) is NONempty. There's a list of other test conditions here, as well as in the man page for the test
command.
Note that a conditional expression in [[ ... ]]
is subtly different from a test command [ ... ]
-- see BashFAQ #031 for differences and a list of available tests. And they're both different from an arithmetic expression with (( ... ))
, and completely different from a subshell with( ... )
...