tags:

views:

961

answers:

11

I have some files that I'd like to delete the last newline if it is the last character in a file. 'od -c' shows me that the command I run does write the file with a trailing new line:

0013600   n   t  >  \n

I've tried a few tricks with sed but the best I could think of isn't doing the trick:

sed -e '$s/\(.*\)\n$/\1/' abc

Any ideas how to do this?

+2  A: 
$  perl -e 'local $/; $_ = <>; s/\n$//; print' a-text-file.txt

See also Match any character (including newlines) in sed.

Sinan Ünür
That takes out all the newlines. Equivalent to `tr -d '\n'`
Dennis Williamson
@Dennis Williamson: noted and corrected.
Sinan Ünür
This works good too, probably less blasphemous than paviums's.
Todd Partridge
Sinan, although Linux and Unix might define text files to end with a newline, Windows poses no such requirement. Notepad, for example, will write only the characters you type without adding anything extra at the end. C compilers might require a source file to end with a line break, but C source files aren't "just" text files, so they can have extra requirements.
Rob Kennedy
in that vein, most javascript/css minifiers will remove trailing newlines, and yet produce text files.
ysth
@Rob Kennedy and @ysth: There is an interesting argument there as to why such files are not actually text files and such.
Sinan Ünür
+10  A: 
perl -pe 'chop if eof' filename >filename2

This was described as a 'perl blasphemy' on the awk website I saw.

But, in a test, it worked.

pavium
Yes, it does work.
Dennis Williamson
You can make it safer by using `chomp`. And it beats slurping the file.
Sinan Ünür
Blasphemy though it is, it works very well. perl -i -pe 'chomp if eof' filename. Thank you.
Todd Partridge
The funny thing about blasphemy and heresy is it's usually hated because it's correct. :)
Ether
+1  A: 
head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile

Edit 2:

Here is an awk version (corrected) that doesn't accumulate a potentially huge array:

awk '{if (line) print line; line=$0} END {printf $0}' abc

Dennis Williamson
Good original way to think about it. Thanks Dennis.
Todd Partridge
the awk version removes empty lines as well..
ghostdog74
You are correct. I defer to your `awk` version. It takes *two* offsets (and a different test) and I only used one. However, you could use `printf` instead of `ORS`.
Dennis Williamson
+3  A: 

gawk

   awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
ghostdog74
Still looks like a lot of characters to me... learning it slowly :). Does the job though. Thanks ghostdog.
Todd Partridge
+1  A: 

Yet another perl WTDI:

perl -i -p0777we's/\n\z//' filename
ysth
A: 

The only time I've wanted to do this is for code golf, and then I've just copied my code out of the file and pasted it into an echo -n 'content'>file statement.

dlamblin
Hey now... it works.
dlamblin
+3  A: 

If you want to do it right, you need something like this:

use autodie qw(open sysseek sysread truncate);

my $file = shift;
open my $fh, '+>>', $file;
my $pos = tell $fh;
sysseek $fh, $pos - 1, 0;
sysread $fh, my $buf, 1 or die 'No data to read?';

if($buf eq "\n"){
    truncate $fh, $pos - 1;
}

We open the file for reading and appending; opening for appending means that we are already seeked to the end of the file. We then get the numerical position of the end of the file with tell. We use that number to seek back one character, and then we read that one character. If it's a newline, we truncate the file to the character before that newline, otherwise, we do nothing.

This runs in constant time and constant space for any input, and doesn't require any more disk space, either.

jrockway
but that has the disadvantage of not reseting ownership/permissions for the file...err, wait...
ysth
A: 

Here is a nice, tidy Python solution. I made no attempt to be terse here.

This truncates a file by two bytes if the last two bytes are CR/LF, or by one byte if the last byte is LF. It does not attempt to modify the file if the last byte(s) are not (CR)LF. It handles errors. Tested in Python 2.6.

Put this in a file called "striplast" and chmod +x striplast.

#!/usr/bin/python

# strip newline from last line of a file


import sys

def trunc(filename, new_len):
    try:
        # open with mode "append" so we have permission to modify
        # cannot open with mode "write" because that clobbers the file!
        f = open(filename, "ab")
        f.truncate(new_len)
        f.close()
    except IOError:
        print "cannot write to file:", filename
        sys.exit(2)

# get input argument
if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = "--help"  # wrong number of arguments so print help

if filename == "--help" or filename == "-h" or filename == "/?":
    print "Usage: %s <filename>" % sys.argv[0]
    print "Strips a newline off the last line of a file."
    sys.exit(1)


try:
    # must have mode "b" (binary) to allow f.seek() with negative offset
    f = open(filename, "rb")
except IOError:
    print "file does not exist:", filename
    sys.exit(2)


SEEK_EOF = 2
f.seek(-2, SEEK_EOF)  # seek to two bytes before end of file

end_pos = f.tell()

line = f.read()
f.close()

if line.endswith("\r\n"):
    trunc(filename, end_pos)
elif line.endswith("\n"):
    trunc(filename, end_pos + 1)

P.S. In the spirit of "Perl golf", here's my shortest Python solution. It slurps the whole file from standard input into memory, strips all newlines off the end, and writes the result to standard output. Not as terse as the Perl; you just can't beat Perl for little tricky fast stuff like this.

Remove the "\n" from the call to .rstrip() and it will strip all white space from the end of the file, including multiple blank lines.

Put this into "slurp_and_chomp.py" and then run python slurp_and_chomp.py < inputfile > outputfile.

import sys

sys.stdout.write(sys.stdin.read().rstrip("\n"))
steveha
A: 
sed ':a;/^\n*$/{$d;N;};/\n$/ba' file
A: 

Using dd:

file='/path/to/file'
[[ "$(tail -c 1 "${file}" | tr -dc '\n' | wc -c)" -eq 1 ]] && \
    printf "" | dd  of="${file}" seek=$(($(stat -f "%z" "${file}") - 1)) bs=1 count=1
    #printf "" | dd  of="${file}" seek=$(($(wc -c < "${file}") - 1)) bs=1 count=1
cpit
A: 

Assuming Unix file type and you only want the last newline this works.

sed -e '${/^$/d}'

It will not work on multiple newlines...

LoranceStinson