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"; fiwasn't working – DJMcMayhem Sep 18 '19 at 20:34 -
@DJMcMayhem: in addition to the trimming issue,
$'..'(singlequotes) is for interpreting escapes like\nbut$".."(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_onlyfile. It will be like:# e.g. in Bash < your_file cat <(printf '\n') - | tail -c 1 | cmp -s <(printf '\n') -tailreading from a pipe cannot seek.catneeds to read the entireyour_file, only thentailcan do its job.tail -c 1 your_fileor<your_file tail -c 1may 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 (bashwill 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