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

- 829,060

- 209
3 Answers
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

- 829,060
-
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
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 entireyour_file
, only thentail
can do its job.tail -c 1 your_file
or<your_file tail -c 1
may be smart enough to seek withinyour_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 spawncmp
; it uses[
which is a builtin in many shells.

- 21,864
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.

- 953
-
1If 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
echo >> file
. I am dealing with many files, some of which have this condition, and I would prefer to determine this without having to openvim
. – user664833 Sep 18 '19 at 20:24