How to find files that don't have empty line at the end?

To clarify, the LF (aka \n or newline) character is the line delimiter, it's not the line separator. A line is not finished unless it's terminated by a newline character. A file that only contains a\nb is not a valid text file because it contains characters after the last line. Same for a file that contains only a. A file that contains a\n contains one non-empty line.

So a file that ends with at least one empty line ends with two newline characters or contains a single newline character.


 tail -c 2 file | od -An -vtc

Outputs \n or \n \n, then the file contains at least one trailing empty line. If it outputs nothing, then that's an empty file, if it outputs <anything-but-\0> \n, then it ends in a non-empty line. Anything else, it's not a text file.

Now, to use that to find files that end in an empty line, OK that's efficient (especially for large files) in that it only reads the last two bytes of the files, but first the output is not easily parsable programmatically especially considering that it's not consistent from one implementation of od to the next, and we'd need to run one tail and one od per file.

find . -type f -size +0 -exec gawk '
  ENDFILE{if ($0 == "") print FILENAME}' {} +

(to find files ending in an empty line) would run as few commands as possible but would mean reading the full content of all files.

Ideally, you'd need a shell that can read the end of a file by itself.

With zsh:

zmodload zsh/system
for f (**/*(D.L+0)) {
    sysseek -w end -2
    [[ $REPLY = $'\n' || $REPLY = $'\n\n' ]] && print -r -- $f
  } < $f

With gnu sed and a shell like zsh (or bash with shopt -s globstar):

sed -ns '${/./F}' ./**/*.styl

this checks if the last line of each file is not empty, if so it prints the filename.
If you want the opposite (print file names if the last line is empty) just replace /./ with /^$/

A correctly terminated text file with an empty last line ends in two \n.

Then, we expect that tail -c2 must be equal to $'\n\n'.

Sadly command expansions remove trailing new lines. We will need a bit of tweaking.

t=$(tail -c2 $f; printf x)  # capture the last two characters.
r="${nl}${nl}$"                 # regex for: "ends in two newlines".
[[ ${t%x} =~ $r ]] &&  echo "file $f ends in an empty line"

We could even expand a bit to check which files fail to have a trailing new line:

find . -type f -name '*.styl' | while read f; do
    t=$(tail -c2 $f; printf x); r1="${nl}$"; r2="${nl}${r1}"
    [[ ${t%x} =~ $r1 ]] || echo "file $f is missing a trailing newline"
    [[ ${t%x} =~ $r2 ]] && echo "$f"

Note that the newline could be changed to something like $'\r\n if needed.
In that case, also change tail -c2 to tail -c4.