Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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).

like image 601
Caife Avatar asked Jun 24 '13 04:06

Caife


People also ask

How do I read the last line of a file in shell?

To look at the last few lines of a file, use the tail command. tail works the same way as head: type tail and the filename to see the last 10 lines of that file, or type tail -number filename to see the last number lines of the file.

How do you skip a line in a shell script?

Using head to get the first lines of a stream, and tail to get the last lines in a stream is intuitive. But if you need to skip the first few lines of a stream, then you use tail “-n +k” syntax. And to skip the last lines of a stream head “-n -k” syntax.

How do I read a text file line by line in shell?

Syntax: Read file line by line on a Bash Unix & Linux shell file. The -r option passed to read command prevents backslash escapes from being interpreted. Add IFS= option before read command to prevent leading/trailing whitespace from being trimmed. while IFS= read -r line; do COMMAND_on $line; done < input.

How read file line by line in shell script and store each line in a variable?

We use the read command with -r argument to read the contents without escaping the backslash character. We read the content of each line and store that in the variable line and inside the while loop we echo with a formatted -e argument to use special characters like \n and print the contents of the line variable.


1 Answers

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.

like image 171
rici Avatar answered Oct 01 '22 17:10

rici