282

Using version control systems I get annoyed at the noise when the diff says No newline at end of file.

So I was wondering: How to add a newline at the end of a file to get rid of those messages?

Toby Speight
  • 8,678
k0pernikus
  • 15,463

19 Answers19

303

Here you go:

sed -i -e '$a\' file

And alternatively for OS X sed:

sed -i '' -e '$a\' file

This adds \n at the end of the file only if it doesn’t already end with a newline. So if you run it twice, it will not add another newline:

$ cd "$(mktemp -d)"
$ printf foo > test.txt
$ sed -e '$a\' test.txt > test-with-eol.txt
$ diff test*
1c1
< foo
\ No newline at end of file
---
> foo
$ echo $?
1
$ sed -e '$a\' test-with-eol.txt > test-still-with-one-eol.txt
$ diff test-with-eol.txt test-still-with-one-eol.txt
$ echo $?
0

How it works:

  • $ denotes the end of file
  • a\ appends the following text (which is nothing, in this case) on a new line

In other words, if the last line contains a character that is not newline, append a newline.

l0b0
  • 51,350
  • 1
    @jwd: From man sed: $ Match the last line. But maybe it works only by accident. Your solution also works. – l0b0 Feb 20 '12 at 11:54
  • 1
    Your solution is also more elegant, and I've tested & committed it, but how can it work? If $ matches the last line, why doesn't it add another newline to a string which already contains a newline? – l0b0 Feb 20 '12 at 12:09
  • 36
    There are two different meanings of $. Inside a regex, such as with the form /<regex>/, it has the usual "match end of line" meaning. Otherwise, used as an address, sed gives it the special "last line in file" meaning. The code works because sed by default appends a newline to its output if it is not already there. The code "$a" just says "match the last line of the file, and add nothing to it." But implicitly, sed adds the newline to every line it processes (such as this $ line) if it is not already there. – jwd Feb 22 '12 at 19:07
  • 1
    Regarding the manpage: The quote you are referring to is under the "Addresses" section. Putting it inside /regex/ gives it a different meaning. The FreeBSD manpages are a little more informative, I think: http://www.freebsd.org/cgi/man.cgi?query=sed – jwd Feb 22 '12 at 19:15
  • For me sed -e '$a\' test.txt just produces empty output, no matter what the contents of text.txt. – Olhovsky Apr 12 '13 at 01:45
  • @Olhovsky Which version of sed? – l0b0 Jun 26 '13 at 13:53
  • @l0b0 sed version 4.2.1-9 – Olhovsky Jun 29 '13 at 11:30
  • 5
    If the file already ends in a newline, this doesn't change it, but it does rewrite it and update its timestamp. That may or may not matter. – Keith Thompson Feb 19 '16 at 19:01
  • @l0b0 This seems to be not working in Cygwin. – Utku Mar 08 '17 at 06:24
  • @Utku Cygwin is a special case, and may handle newlines differently. Please ask a separate question, with enough detail to reproduce exactly what behaviour you're seeing. – l0b0 Mar 08 '17 at 08:25
  • This "solution" also doesn't work for sed on SCO OpenServer 5.0.7. Not only is -i not allowed, but also, this removes the last line of the file - probably because it is not a line according to POSIX. – kbulgrien Apr 24 '18 at 20:01
  • @kbulgrien Trying to write a shell script that works on every *nix platform ever is a pointless exercise - and yes, I've seen it done. It is going to massively hobble your development since you can only use a small subset of the features available on the vast majority of systems in use right now. Much better to write a script for the actual target platform(s) and then patch or even rewrite as needed. – l0b0 Apr 24 '18 at 20:58
  • Isn't sed '$q' clearer? q means to quit, instead of appending nothing. – dosentmatter Dec 15 '18 at 07:03
  • 1
    if you use fish (the shell) it seems you need to escape the backslash like: sed -i -e '$a\\' file – Tom Saleeba Mar 18 '19 at 03:03
  • Much after the fact but FYI this works just fine with Cygwin IF the source file has LF ("unix style") line terminators. If the source file has CRLF ("windows style") line terminators it will succeed but you'll be left with a file with mixed line endings (CRLF everywhere but the last line). (You could add a call to dos2unix or unix2dos if so inclined.) – B Layer Jul 05 '19 at 07:45
  • 2
    @dosentmatter "Isn't sed '$q' clearer? q means to quit, instead of appending nothing." I tested sed '$q' with GNU sed 4.4, it didn't work. q just quits without doing anything. a\ has some extra logic that will add a trailing newline if it doesn't exist. – wisbucky Sep 25 '19 at 23:31
  • How do you make this ignore binary files and only operate on text files? – Aaron Franke Mar 19 '20 at 21:19
  • @AaronFranke You're going to need some heuristic to determine which files are "binary", which is not well defined. The file command is probably your best bet. – l0b0 Mar 19 '20 at 21:29
  • @I0b0, why in your answer do you suggest sed -e '$a\', but in https://github.com/l0b0/tilde/blob/e7ccc9a6a3aba2b0216c8e5141554e8a729389ec/.bash_aliases#L493 you use sed -e '/.$/a\'? – Derek Mahar May 27 '20 at 17:22
  • @DerekMahar I believe they are equivalent. Also, I don't keep my repo in sync with Stack Overflow. – l0b0 May 27 '20 at 20:07
  • In order to get this working in BSD unix, I had to put a literal newline after the backslash (e.g. use two lines). It would not work as shown nor with doubled backslash. BSD uses some version of sed that doesn't even have the capability of showing its own version (!) – user9645 Aug 03 '21 at 19:12
  • Also in BSD unix it ALWAYS adds a newline at the end, not just if the file lacks one. – user9645 Aug 03 '21 at 19:23
  • @jwd Does it though? Try $s/.*/&/ instead of '$a\'. GNU Sed has processed the line (whatever "process" means here), and yet no newline character is appended to the last pseudo-line. This answer is just a kludge that relies on a specific interpretation of the standard, and it is no wonder there are comments saying that the command works differently on their Sed implementations. – Quasímodo Dec 14 '21 at 15:48
  • This code is broken as the backslash escapes the second '. – MKesper Jan 26 '22 at 07:30
  • @MKesper No, that's not how single quotes work in Bash. – l0b0 Jan 26 '22 at 10:14
  • beautiful answer. – arod Jun 14 '22 at 22:15
  • 2
    not working for me on the OS X 13.4 version of sed. No error, but does not append a newline. – Michael Johnston Jul 06 '23 at 21:04
  • So if sed '$a\' for GNU-sed not available, just use awk 1 like @dave_thompson_085 said. – Johnny Wong Oct 11 '23 at 09:17
96

To recursively sanitize a project I use this oneliner:

git ls-files -z | while IFS= read -rd '' f; do if file --mime-encoding "$f" | grep -qv binary; then tail -c1 < "$f" | read -r _ || echo >> "$f"; fi; done

Explanation:

  • git ls-files -z lists files in the repository. It takes an optional pattern as additional parameter which might be useful in some cases if you want to restrict the operation to certain files/directories. As an alternative, you could use find -print0 ... or similar programs to list affected files - just make sure it emits NUL-delimited entries.

  • while IFS= read -rd '' f; do ... done iterates through the entries, safely handling filenames that include whitespace and/or newlines.

  • if file --mime-encoding "$f" | grep -qv binary checks whether the file is in a binary format (such as images) and skips those.

  • tail -c1 < "$f" reads the last char from a file.

  • read -r _ exits with a nonzero exit status if a trailing newline is missing.

  • || echo >> "$f" appends a newline to the file if the exit status of the previous command was nonzero.

  • 2
    You can also do it like this if you want to just sanitize a subset of your files: find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done – Per Lundberg Mar 22 '19 at 09:54
  • 1
    @PerLundberg you can also pass a pattern to git ls-files which will still save you from editing files that are not tracked in version control. – Patrick Oscity Mar 22 '19 at 12:58
  • 1
    @StéphaneChazelas adding the IFS= to unset the separator is good to preserve surrounding whitespace. The null terminated entries are only relevant if you have files or directories with a newline in their name, which seems kind of far fetched, but is the more correct way to handle the generic case, I agree. Just as a small caveat: the -d option to read is not available in POSIX sh. – Patrick Oscity Mar 22 '19 at 13:26
  • Yes, hence my zsh/bash's. See also my use of tail -n1 < "$f" to avoid problems with file names that start with - (tail -n1 -- "$f" doesn't work for the file called -). You may want to clarify that the answer is now zsh/bash specific. – Stéphane Chazelas Mar 22 '19 at 13:30
  • Actually, { tail -c1 | read -r _ || echo; } < "$f" >> "$f" would be better to avoid adding the newline if the file can't be open for reading (for instance because it doesn't exist or is write-only). Reading one byte of that last line instead of the full line would be enough. – Stéphane Chazelas Mar 22 '19 at 14:35
  • @StéphaneChazelas all valid points. Thank you. – Patrick Oscity Mar 22 '19 at 14:45
  • For me, touch - && tail -n1 -- "-" is working fine. Are you sure this is an issue? – Patrick Oscity Mar 22 '19 at 14:50
  • The GNU, busybox, ast-open, Solaris tail implementations at least (not {Free,Net,Open}BSD's) treat - as meaning standard input instead of the file called - in the current directory (as allowed but not required by POSIX). – Stéphane Chazelas Mar 22 '19 at 16:02
  • @StéphaneChazelas I tried to incorporate all of your suggestions, but tried to omit curly braces to make it easier to read left-to-right. You seem to know your shell stuff, so I'd be happy about final remarks :) Cheers! – Patrick Oscity Mar 23 '19 at 22:59
  • How do you make this ignore binary files and only operate on text files? – Aaron Franke Mar 19 '20 at 21:19
  • 1
    @AaronFranke replace git ls-files -z with git grep -zIl '' – Patrick Oscity Mar 19 '20 at 22:20
  • Awesome, that works. Now, how do I exclude specific file extensions? – Aaron Franke Mar 19 '20 at 22:25
  • @AaronFranke you could pipe through a grep -v or you could take a look at the man page of git-grep. It has some examples for excluding files. https://git-scm.com/docs/git-grep#_examples Should be smth like git grep -zIl '' -- ':!*.xml' ':!*.json' – Patrick Oscity Mar 20 '20 at 09:40
  • Fantastic demonstration of the Unix philosophy. Good work @PatrickOscity. – naltun Aug 13 '21 at 20:18
  • I found that this corrupted image files in my repo. To avoid, that test if a file is binary before adding a new line: git ls-files -z | while IFS= read -rd '' f; do if file --mime-encoding "$f" | grep -qv binary ; then tail -c1 < "$f" | read -r _ || echo >> "$f"; fi; done – Stephen Ostermiller Nov 15 '22 at 22:46
  • Thanks @StephenOstermiller great addition! I will update the answer. – Patrick Oscity Nov 18 '22 at 14:33
  • The best answer I've seen. Thanks! – alan23273850 Feb 06 '24 at 10:43
48

Have a look:

$ echo -n foo > foo 
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo

so echo "" >> noeol-file should do the trick. (Or did you mean to ask for identifying these files and fixing them?)

edit removed the "" from echo "" >> foo (see @yuyichao's comment) edit2 added the "" again (but see @Keith Thompson's comment)

sr_
  • 15,384
  • 5
    the "" is not necessary (at least for bash) and tail -1 | wc -l can be used to find out the file without a new line at the end – yuyichao Feb 17 '12 at 14:42
  • 6
    @yuyichao: The "" isn't necessary for bash, but I've seen echo implementations that print nothing when invoked without arguments (though none of the ones I can find now do this). echo "" >> noeol-file is probably slightly more robust. printf "\n" >> noeol-file is even more so. – Keith Thompson Feb 17 '12 at 17:17
  • 2
    @KeithThompson, csh's echo is the one known to output nothing when not passed any argument. But then if we're going to support non-Bourne-like shells, we should make it echo '' instead of echo "" as echo "" would ouput ""<newline> with rc or es for instance. – Stéphane Chazelas Feb 19 '16 at 11:49
  • 1
    @StéphaneChazelas: And tcsh, unlike csh, prints a newline when invoked with no arguments -- regardless of the setting of $echo_style. – Keith Thompson Feb 19 '16 at 18:55
  • 1
    Doesn't this cause an extra newline to be added to files that already have newlines? – Aaron Franke Mar 19 '20 at 21:21
  • this adds an empty line even when already a newline is there – αғsнιη May 02 '21 at 04:42
26

A simple, portable, POSIX-compliant way to add an absent, final newline to a would be text file:

[ -n "$(tail -c1 file)" ] && echo >> file

This approach does not need to read the entire file; it can simply seek to EOF and work from there.

This approach also does not need to create temp files behind your back (e.g. sed -i), so hardlinks aren't affected.

echo appends a newline to the file only when the result of the command substitution is a non-empty string. Note that this can only happen if the file is not empty and the last byte is not a newline.

If the last byte of the file is a newline, tail returns it, then command substitution strips it; the result is an empty string. The -n test fails and echo does not run.

If the file is empty, the result of the command substitution is also an empty string, and again echo does not run. This is desirable, because an empty file is not an invalid text file, nor is it equivalent to a non-empty text file with an empty line.

Barefoot IO
  • 1,946
  • 2
    Note that it doesn't work with yash if the last character in the file is a multi-byte character (in UTF-8 locales for instance), or if the locale is C and the last byte in the file has the 8th bit set. With other shells (except zsh), it would not add a newline if the file ended in a NUL byte (but then again, that would mean the input would be non-text even after a newline is added). – Stéphane Chazelas Feb 19 '16 at 11:39
  • 1
  • Should it be echo '\n' intead of echo? echo by itself does not work for me in docker file – Yogesh Jindal Aug 03 '22 at 06:03
  • Does this add only LF and not CRLF (in case some files may be using Windows-1252 encoding)? – MasterHD Apr 03 '23 at 13:36
21

Another solution using ed. This solution only affect the last line and only if \n is missing:

ed -s file <<< w

It essentially works opening the file for editing through a script, the script is the single w command, that write the file back to disk. It is based on this sentence found in ed(1) man page:

LIMITATIONS
       (...)

       If  a  text (non-binary) file is not terminated by a newline character,
       then ed appends one on reading/writing it.  In the  case  of  a  binary
       file, ed does not append a newline on reading/writing.
enzotib
  • 51,661
20

Add newline regardless:

echo >> filename

Here is a way to check if a newline exists at the end before adding one, by using Python:

f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
Alexander
  • 9,850
  • 1
    I wouldn't use the python version in any sort of loop because of the slow python startup time. Of course you could do the loop in python if you wanted. – Kevin Cox Nov 09 '13 at 14:03
  • 3
    The startup time for Python is 0.03 seconds here. Do you really consider that to be problematic? – Alexander Nov 10 '13 at 11:48
  • That's quite a bit faster than mine, but it adds up quick. I remember using python to do some math in a script once and it really bogged it down. Maybe they have improved it since then and I just have haunting memories. – Kevin Cox Nov 10 '13 at 15:36
  • 5
    Startup time does matter if you call python in a loop, that is why I said consider doing the loop in python. Then you only incur the startup cost once. For me, half the cost the startup is more than half of the time of the whole snipit, I would consider that substantial overhead. (Again, irrelevant if only doing a small number of files) – Kevin Cox Nov 11 '13 at 16:35
  • 4
    echo "" seems more robust than echo -n '\n'. Or you could use printf '\n' – Keith Thompson Feb 19 '16 at 18:58
  • Thanks for the comment! Actually just echo >> file seems to work just fine. Updated my answer. – Alexander Feb 20 '16 at 12:39
  • 2
    This worked fine for me – Daniel Gomez Rico Jan 18 '19 at 22:58
  • Running the Python code in a loop is possible, but the question is about shell commands. 0.03 is not bad for a shell command, even in a loop. – Alexander Nov 23 '20 at 11:21
12

The fastest solution is:

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 

  1. Is really fast.
    On a medium size file seq 99999999 >file this takes miliseconds.
    Other solutions take a long time:

    [ -n "$(tail -c1 file)" ] && printf '\n' >>file  0.013 sec
    vi -ecwq file                                    2.544 sec
    paste file 1<> file                             31.943 sec
    ed -s file <<< w                             1m  4.422 sec
    sed -i -e '$a\' file                         3m 20.931 sec
    
  2. Works in ash, bash, lksh, mksh, ksh93, attsh and zsh but not yash.

  3. Does not change file timestamp if there is no need to add a newline.
    All other solutions presented here change the timestamp of file.
  4. All solutions above are valid POSIX.

If you need a solution portable to yash (and all other shells listed above), it may get a bit more complex:

f=file
if       [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then     printf '\n' >>"$f"
fi
5

The fastest way to test if the last byte of a file is a newline is to read only that last byte. That could be done with tail -c1 file. However, the simplistic way to test if the byte value is a new line, depending on the shell usual removal of a trailing new line inside a command expansion fails (for example) in yash, when the last character in the file is an UTF-8 value.

The correct, POSIX-compliant, all (reasonable) shells way to find if the last byte of a file is a new line is to use either xxd or hexdump:

tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'

Then, comparing the output of above to 0A will provide a robust test.
It is useful to avoid adding a new line to an otherwise empty file.
File that will fail to provide a last character of 0A, of course:

f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"

Short and sweet. This takes very little time as it just reads the the last byte (seek to EOF). It does not matter if the file is big. Then only add one byte if needed.

No temp files needed nor used. No hardlinks are affected.

If this test is run twice, it will not add another newline.

4

At least in the GNU versions, simply grep '' or awk 1 canonicalizes its input, adding a final newline if not already present. They do copy the file in the process, which takes time if large (but source shouldn't be too large to read anyway?) and updates the modtime unless you do something like

 mv file old; grep '' <old >file; touch -r old file

(although that may be okay on a file you are checking-in because you modified it) and it loses hardlinks, nondefault permissions and ACLs etc unless you are even more careful.

3

If you just want to quickly add a newline when processing some pipeline, use this:

outputting_program | { cat ; echo ; }

it's also POSIX compliant.

Then, of course, you can redirect it to a file.

MichalH
  • 2,379
  • 2
    The fact that I can use this in a pipeline is helpful. This allows me to count the number of rows in a CSV file, excluding the header. And it helps get an accurate line count on windows files that don't end with a newline or carriage return.

    cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l

    – Kyle Tolle Dec 22 '15 at 16:54
  • this is not adding missing newline, it adds an empty line even if there is one already exist – αғsнιη May 02 '21 at 04:37
  • @αғsнιη The question wasn't "how to add a newline if it's missing?". It was "how to add a newline?". – MichalH May 07 '21 at 16:03
  • Also you can do echo "$(outputting_program)" which adds the newline only if it was missing. – MichalH May 07 '21 at 16:04
2

Provided there are no nulls in input:

paste - <>infile >&0

...would suffice to always only append a newline to the tail end of an infile if it didn't have one already. And it need only read the input file through the one time to get it right.

Toby Speight
  • 8,678
  • 1
    That won't work like that as stdin and stdout share the same open-file description (so cursor within the file). You'd need paste infile 1<> infile instead. – Stéphane Chazelas Aug 18 '17 at 14:03
1

Although it doesn't directly answer the question, here is a related script I wrote to detect files which do not end in newline. It is very fast.

find . -type f | # sort |        # sort file names if you like
/usr/bin/perl -lne '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

The perl script reads a list of (optionally sorted) file names from stdin and for every file it reads the last byte to determine if the file ends in a newline or not. It is very fast because it avoids reading the entire contents of each file. It outputs one line for each file it reads, prefixed with "error:" if some kind of error occurs, "empty:" if the file is empty (doesn't end with newline!), "EOL:" ("end of line") if the file ends with newline and "no EOL:" if the file doesn't end with newline.

Note: the script doesn't handle file names which contain newlines. If you're on a GNU or BSD system, you could handle all possible file names by adding -print0 to find, -z to sort, and -0 to perl, like this:

find . -type f -print0 | sort -z |
/usr/bin/perl -ln0e '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

Of course, you'd still have to come up with a way of encoding the file names with newlines in the output (left as an exercise for the reader).

The output could be filtered, if desired, to append a newline to those files which don't have one, most simply with

 echo >> "$filename"

Lack of a final newline can cause bugs in scripts since some versions of shell and other utilities will not properly handle a missing final newline when reading such a file.

In my experience, the lack of a final newline is caused by using various Windows utilities to edit files. I have never seen vim cause a missing final newline when editing a file, although it will report on such files.

Finally, there are much shorter (but slower) scripts which can loop over their file name inputs to print those files which do not end in newline, such as:

/usr/bin/perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...
jrw32982
  • 723
1

The vi/vim/ex editors automatically add <EOL> at EOF unless file already has it.

So try either:

vi -ecwq foo.txt

which is equivalent to:

ex -cwq foo.txt

Testing:

$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt

To correct multiple files, check: How to fix 'No newline at end of file' for lots of files? at SO

Why this is so important? To keep our files POSIX compatible.

kenorb
  • 20,988
0

To apply the accepted answer to all files in the current directory (plus subdirectories):

$ find . -type f -exec sed -i -e '$a\' {} \;

This works on Linux (Ubuntu). On OS X you probably have to use -i '' (untested).

  • 4
    Note that find . lists all files, including files in .git. To exclude: find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \; – friederbluemle Jul 14 '15 at 07:59
  • Wish I would have read this comment/thought about it before I ran it. Oh well. – kstev Nov 09 '15 at 04:02
0

You could write a fix-non-delimited-line script like:

#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
  if sysopen -rwu0 -- "$file"; then
    if sysseek -w end -1; then
      read -r x || print -u0
    else
      syserror -p "Can't seek in $file before the last byte: "
      ret=1
    fi
  else
    ret=1
  fi
done
exit $ret

Contrary to some of the solutions given here, it

  • should be efficient in that it doesn't fork any process, only reads one byte for each file, and doesn't rewrite the file over (just appends a newline)
  • will not break symlinks/hardlinks or affect metadata (also, the ctime/mtime are only updated when a newline is added)
  • should work OK even if the last byte is a NUL or is part of a multi-byte character.
  • should work OK regardless of what characters or non-characters the file names may contain
  • Should handle correctly unreadable or unwritable or unseekable files (and report errors accordingly)
  • Should not add a newline to empty files (but reports an error about an invalid seek in that case)

You can use it for instance as:

that-script *.txt

or:

git ls-files -z | xargs -0 that-script

POSIXly, you could do something functionally equivalent with

export LC_ALL=C
ret=0
for file do
  [ -s "$file" ] || continue
  {
    c=$(tail -c 1 | od -An -vtc)
    case $c in
      (*'\n'*) ;;
      (*[![:space:]]*) printf '\n' >&0 || ret=$?;;
      (*) ret=1;; # tail likely failed
    esac
  } 0<> "$file" || ret=$? # record failure to open
done
0

To fix all files in a git repo run

git ls-files --eol |\
 grep -e 'i/lf' |\
 grep -v 'attr/-text' |\
 sed 's/.*\t//' |\
 xargs -d '\n' sed -b -i -e '$a\'
  • git ls-files --eol list all files tracked by git with their eol attribute
  • grep -e 'i/lf' filter files checked into the index with LF
  • grep -v 'attr/-text' skip files that are marked as binary or -text in .gitattributes
  • sed 's/.*\t//' filter out everything but the paths
  • xargs -d '\n' sed -b -i -e '$a\' add a newline at the end of the file
    • -b treat the file as binary (don't touch line endings)
    • -i edits the file in place
    • -e '$a\' add a newline at the end of the file but only if there's no newline at the end of the file and the file isn't empty.
CervEd
  • 174
0
perl -0777pe 's/\R?$/\n/' file

-0 without arguments is equivalent to no record separator (treat the whole file as a single line), so $ equals EOF not EOL.

\R is equivalent at CRLF (Windows) or LF (Linux) or CR (MAC).

Mario Palumbo
  • 233
  • 1
  • 14
-2

Adding to Patrick Oscity's answer, if you just want to apply it to a specific directory, you could also use:

find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done

Run this inside the directory you would like to add newlines to.

cipher
  • 135
-2

echo $'' >> <FILE_NAME> will add a blank line to the end of the file.

echo $'\n\n' >> <FILE_NAME> will add 3 blank lines to the end of the file.

peterh
  • 9,731