9

If you open a file in vim and that file has no EOL at the end of its last line, then the editor will report it as [noeol]. How can I determine this before opening it in vim? (Is there a command I can issue to determine this?)

  • 1
    I'm curious to understand what you would want to do about it if you could determine whether or not the last line is ended with a newline character. Thanks. – Chris Davies Sep 18 '19 at 20:22
  • I would then issue echo >> file. I am dealing with many files, some of which have this condition, and I would prefer to determine this without having to open vim. – user664833 Sep 18 '19 at 20:24
  • Would it be correct to say that the issue you want to identify (and fix) files that have no final newline? Or is there a definite restriction that you want this identification to occur only for files that are about to be edited? – Chris Davies Sep 18 '19 at 20:28
  • Related https://unix.stackexchange.com/q/31947/100397 – Chris Davies Sep 18 '19 at 20:32
  • 2
    Thanks for asking for clarification. The issue is that I want to identify whether a file has no EOL at its end from the command line, plain and simple. I do not have any additional requirements such as identifying this only for files that are about to be edited. – user664833 Sep 18 '19 at 20:44

3 Answers3

14

tail -c 1 outputs the last character (more precisely, the last byte) of its input.

Command substitution strips off a trailing newline, so $(tail -c 1 <…) is empty if the last character of the file is a newline. It's also empty if the last character is a null byte (in most shells), but text files don't have null bytes.

Keep in mind that an empty file doesn't need an extra newline.

if [ ! -s "$filename" ]; then
  echo "$filename is empty"
elif [ -z "$(tail -c 1 <"$filename")" ]; then
  echo "$filename ends with a newline or with a null byte"
else
  echo "$filename does not end with a newline nor with a null byte"
fi
  • Ah, that's a much better answer than mine. I was wondering why if [ $"\n" == "$(cat test | tail -c 1)" ]; then echo "yes"; fi wasn't working – DJMcMayhem Sep 18 '19 at 20:34
  • @DJMcMayhem: in addition to the trimming issue, $'..' (singlequotes) is for interpreting escapes like \n but $".." (doublequotes) is for translating to other languages (more exactly, locales). – dave_thompson_085 Sep 19 '19 at 04:52
1

Create a file with only a newline character in it, then compare it to the last byte of your file:

printf '\n' > newline_only
tail -c 1 your_file | cmp -s newline_only -

I used cmp -s to make cmp basically silent. The exit status 0 indicates there is a newline character at the very end of your_file.

If your_file is empty, the exit status will be 1. You may want to make an exception and get 0 in this case. If so, prepend a newline to what cmp gets via its stdin:

( printf '\n'; tail -c 1 your_file ) | cmp -s newline_only -
# or
cat newline_only your_file | tail -c 1 | cmp -s newline_only -
# or better
<your_file cat newline_only - | tail -c 1 | cmp -s newline_only -

The last one is somewhat better because it will return non-zero exit status if your_file doesn't exist, cannot be read etc. If I were you I would want this. Although if your_file is in fact a directory then cat, tail and cmp will run and you may get 0 and a complaint from cat; or may not, see this: When did directories stop being readable as files?). Therefore you may want some additional logic or option (e.g. set -o pipefail in Bash).

Notes:

  • In some shells you can use process substitution to avoid creating the newline_only file. It will be like:

    # e.g. in Bash
    < your_file cat <(printf '\n') - | tail -c 1 | cmp -s <(printf '\n') -
    
  • tail reading from a pipe cannot seek. cat needs to read the entire your_file, only then tail can do its job. tail -c 1 your_file or <your_file tail -c 1 may be smart enough to seek within your_file. This should be negligible if you test one or few small files though.

  • This other solution will probably perform better: it doesn't create files; it doesn't pipe into tail; it doesn't spawn cmp; it uses [ which is a builtin in many shells.
0

This doesn't really have anything to do with vim. Pretty much you want

tail -c 1 file

to get the last character of the file.

  • 1
    If the last character is a space, or a tab (which displays as one or several spaces), then no character 'appears' (is visible to a human), but the file does have an unterminated last line. – dave_thompson_085 Sep 19 '19 at 04:51
  • I think you can improve that: noeol(){ test "$(tail -c1 "$1"; echo x)" != $'\nx'; } (you can simply use '<litteral newline>x' instead of the non-standard $'...' syntax -- that's impossible to enter in a comment). Unlike @gilles' answer, this does not need special casing for empty, zero bytes, etc (bash will always warn in the latter case, though) –  Sep 20 '19 at 10:22