[bash] Reading input files by line using read command in shell scripting skips last line

I usually use the read command to read an input file to the shell script line by line. An example code such as the one below yields a wrong result if a new line isn't inserted at the end of the last line in the input file, blah.txt.

#!/bin/sh

while read line
do
echo $line
done <blah.txt

So if the input file reads something like -

One 
Two
Three
Four

and I do not hit return after Four, the script fails to read the last line, and prints

One
Two
Three

Now if I leave an extra blank line after Four, like,

One 
Two
Three
Four
//blank line

the output prints all the lines, including Four. However, this is not the case when I read a line using the cat command; all lines including the last get printed without me having to add an extra blank line at the end.

Anyone has ideas on why this happens? The scripts I create will mostly be run by others, so it isn't necessary they're going to add an extra blank line at the end of every input file.

I've been trying to figure this out for ages; I'd appreciate it if you have any solutions(of course, the cat command is one, but I'd like to know the reason behind read not working as well).

This question is related to bash parsing shell file-io

The answer is


read reads until it finds a newline character or the end of file, and returns a non-zero exit code if it encounters an end-of-file. So it's quite possible for it to both read a line and return a non-zero exit code.

Consequently, the following code is not safe if the input might not be terminated by a newline:

while read LINE; do
  # do something with LINE
done

because the body of the while won't be executed on the last line.

Technically speaking, a file not terminated with a newline is not a text file, and text tools may fail in odd ways on such a file. However, I'm always reluctant to fall back on that explanation.

One way to solve the problem is to test if what was read is non-empty (-n):

while read -r LINE || [[ -n $LINE ]]; do
  # do something with LINE
done

Other solutions include using mapfile to read the file into an array, piping the file through some utility which is guaranteed to terminate the last line properly (grep ., for example, if you don't want to deal with blank lines), or doing the iterative processing with a tool like awk (which is usually my preference).

Note that -r is almost certainly needed in the read builtin; it causes read to not reinterpret \-sequences in the input.


One line answer:

IFS=$'\n'; for line in $(cat file.txt); do echo "$line" ; done

Below code with Redirected "while-read" loop works fine for me

while read LINE
do
      let count++
      echo "$count $LINE"

done < $FILENAME

echo -e "\nTotal $count Lines read"

Use while loop like this:

while IFS= read -r line || [ -n "$line" ]; do
  echo "$line"
done <file

Or using grep with while loop:

while IFS= read -r line; do
  echo "$line"
done < <(grep "" file)

Using grep . instead of grep "" will skip the empty lines.

Note:

  1. Using IFS= keeps any line indentation intact.

  2. You should almost always use the -r option with read.

  3. Don't read lines with for

  4. File without a newline at the end isn't a standard unix text file.


DONE=false
until $DONE
do
  read line || DONE=true
  echo $line
done < blah.txt

How to use `while read` (Bash) to read the last line in a file if there’s no newline at the end of the file?


Examples related to bash

Comparing a variable with a string python not working when redirecting from bash script Zipping a file in bash fails How do I prevent Conda from activating the base environment by default? Get first line of a shell command's output Fixing a systemd service 203/EXEC failure (no such file or directory) /bin/sh: apt-get: not found VSCode Change Default Terminal Run bash command on jenkins pipeline How to check if the docker engine and a docker container are running? How to switch Python versions in Terminal?

Examples related to parsing

Got a NumberFormatException while trying to parse a text file for objects Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) Python/Json:Expecting property name enclosed in double quotes Correctly Parsing JSON in Swift 3 How to get response as String using retrofit without using GSON or any other library in android UIButton action in table view cell "Expected BEGIN_OBJECT but was STRING at line 1 column 1" How to convert an XML file to nice pandas dataframe? How to extract multiple JSON objects from one file? How to sum digits of an integer in java?

Examples related to shell

Comparing a variable with a string python not working when redirecting from bash script Get first line of a shell command's output How to run shell script file using nodejs? Run bash command on jenkins pipeline Way to create multiline comments in Bash? How to do multiline shell script in Ansible How to check if a file exists in a shell script How to check if an environment variable exists and get its value? Curl to return http status code along with the response docker entrypoint running bash script gets "permission denied"

Examples related to file-io

Python, Pandas : write content of DataFrame into text File Saving response from Requests to file How to while loop until the end of a file in Python without checking for empty line? Getting "java.nio.file.AccessDeniedException" when trying to write to a folder How do I add a resources folder to my Java project in Eclipse Read and write a String from text file Python Pandas: How to read only first n rows of CSV files in? Open files in 'rt' and 'wt' modes How to write to a file without overwriting current contents? Write objects into file with Node.js