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?)
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
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 -
cmp -s to make
cmp basically silent. The exit status
0 indicates there is a newline character at the very end of
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
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).
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 entire
your_file, only then
tailcan do its job.
tail -c 1 your_fileor
<your_file tail -c 1may 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.