3

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. – roaima 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? – roaima Sep 18 '19 at 20:28
  • Related unix.stackexchange.com/q/31947/100397 – roaima 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
4

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 4:52
1

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 4: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) – mosvy Sep 20 '19 at 10:22
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.