Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I have files that end in one or more newlines and should end in only one newline. How can I do that with Bash/Unix/GNU tools?

Example bad file:

1\n
\n
2\n
\n
\n
3\n
\n
\n
\n

Example corrected file:

1\n
\n
2\n
\n
\n
3\n

In other words: There should be exactly one newline between the EOF and the last non-newline character of the file.

Reference Implementation

Read file contents, chop off a single newline till there no further two newlines at the end, write it back:

#! /bin/python

import sys

with open(sys.argv[1]) as infile:
    lines = infile.read()

while lines.endswith("\n\n"):
    lines = lines[:-1]

with open(sys.argv[2], 'w') as outfile:
    for line in lines:
        outfile.write(line)

Clarification: Of course, piping is allowed, if that is more elegant.

share|improve this question

8 Answers 8

up vote 10 down vote accepted
awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; nlstack=""; print;}' file
share|improve this answer
    
+1: awk's solutions are (almost) always elegant and readable! –  Olivier Dulac Jul 4 '13 at 9:37
    
@OlivierDulac Indeed. When I saw the sed proposal I just thought OMG... –  Hauke Laging Jul 4 '13 at 11:32
    
this doesn't work on OSX Mavericks using the latest available awk from Homebrew. It errors with awk: illegal statement. brew install mawk and changing the command to mawk works though. –  tjmcewan May 9 at 5:02

Since you already have answers with the more suitable tools sed and awk; you could take advantage of the fact that $(< file) strips off trailing blank lines.

a=$(<file); printf '%s\n' "$a" > file

That cheap hack wouldn't work to remove trailing blank lines which may contain spaces or other non-printing characters, only to remove trailing empty lines. It also won't work if the file contains null bytes.

In shells other than bash and zsh, use $(cat file) instead of $(<file).

share|improve this answer
    
+1 to point out what looks like a bug to me : $(<file) isn't really reading the file? why does it discard trailing newlines? (it does, i just tested, thanks for pointing it out!) –  Olivier Dulac Jul 4 '13 at 9:23
2  
@OlivierDulac $() discards trailing newlines. That's a design decision. I assume that this shall make the integration in other strings easier: echo "On $(date ...) we will meet." would be evil with the newline that nearly every shell command outputs at the end. –  Hauke Laging Jul 4 '13 at 11:31
    
@HaukeLaging: good point, it's probably the source of that behaviour –  Olivier Dulac Jul 4 '13 at 12:13
    
I added a special case to avoid appending "\n" to empty files: [[ $a == '' ]] || printf '%s\n' "$a" >"$file". –  davidchambers Apr 14 at 19:11

From useful one-line scripts for sed.

# Delete all trailing blank lines at end of file (only).
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file
share|improve this answer
    
Thanks, I used the following to do it in place for multiple files: find . -type f -name '*.js' -exec sed --in-place -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \; –  jakub.g Nov 22 '13 at 9:48

You can use this trick with cat & printf:

$ printf '%s\n' "`cat file`"

For example

$ printf '%s\n' "`cat ifile`" > ofile
$ cat -e ofile
1$
$
2$
$
$
3$

The $ denotes the end of a line.

References

share|improve this answer
1  
may fail if file contains e.g. -n. Use printf instead of echo. –  Gilles Jul 5 '13 at 22:59
    
@Gilles - thanks I've fixed per your feedback. –  slm Jul 5 '13 at 23:15
    
This exploits the fact that `` strips any newlines at EOF. See the other answer for a discussion. –  Bengt Jul 7 '13 at 14:52
    
@Bengt - which discussion, the link you provided doesn't go to anything specific in this thread. –  slm Jul 7 '13 at 23:19
    
@slm It should be equivalent to this one: unix.stackexchange.com/questions/81685/… –  Bengt Jul 8 '13 at 0:00

If your file is small enough to slurp into memory, you can use this

perl -e 'local($/);$f=<>; $f=~s/\n*$/\n/;print $f;' file
share|improve this answer

Here's a Perl solution that doesn't require reading more than one line into memory at a time:

my $n = 0;
while (<>) {
    if (/./) {
        print "\n" x $n, $_;
        $n = 0;
    } else {
        $n++;
    }
}

or, as a one-liner:

perl -ne 'if (/./) { print "\n" x $n, $_; $n = 0 } else { $n++ }'

This reads the file a line at a time and checks each line to see if contains a non-newline character. If it doesn't, it increments a counter; if it does, it prints the number of newlines indicated by the counter, followed by the line itself, and then resets the counter.

Technically, even buffering a single line in memory is unnecessary; it would be possible to solve this problem using a constant amount of memory by reading the file in fixed-length chunks and processing it character by character using a state machine. However, I suspect that would be needlessly complicated for the typical use case.

share|improve this answer

In python (I know it is not what you want, but it is much better as it is optimized, and a prelude to the bash version) without rewriting the file and without reading all the file (which is a good thing if the file is very large):

#!/bin/python
import sys
infile = open(sys.argv[1], 'r+')
infile.seek(-1, 2)
while infile.read(1) == '\n':
  infile.seek(-2, 1)
infile.seek(1, 1)
infile.truncate()
infile.close()

Note that it does not work on files where the EOL character is not '\n'.

share|improve this answer

A bash version, implementing the python algorithm, but less efficient as it needs many processes:

#!/bin/bash
n=1
while test "$(tail -n $n "$1")" == ""; do
  ((n++))
done
((n--))
truncate -s $(($(stat -c "%s" "$1") - $n)) "$1"
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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