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

filesnewlinessearch

I have files in subdirectories of the current directory that may or may not have new lines at the end; how can I find files that don't have a newline at the end?

I've tried this:

find . -name '*.styl' | while read file; do
    awk 'END{print}' $file | grep -E '^$' > /dev/null || echo $file;
done

but it doesn't work. awk 'END{print}' $file prints the line before an empty new line, the same as tail -n 1 $file.

Best Answer

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.

If:

 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
    sysread
    [[ $REPLY = $'\n' || $REPLY = $'\n\n' ]] && print -r -- $f
  } < $f
}
Related Question