181

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?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Todd Partridge 'Gen2ly'
  • 2,258
  • 2
  • 19
  • 18
  • 4
    newline is only one character for unix newlines. DOS newlines are two characters. Of course, literal "\n" is two characters. Which are you actually looking for? – Dennis Williamson Oct 31 '09 at 10:47
  • 3
    Although the representation might be `\n`, in linux is is *one* character – pavium Oct 31 '09 at 10:49
  • 10
    Can you elaborate on why you want to do this? Text files are _supposed_ to end with an end-of-line, unless they are entirely empty. It seems strange to me that you'd want to have such a truncated file? – Thomas Padron-McCarthy Oct 31 '09 at 11:06
  • The usual reason for doing *something* like this is to delete a trailing comma from the last line of a CSV file. Sed works well, but newlines have to be treated differently. – pavium Oct 31 '09 at 11:15
  • Yeah this is for Linux so thanks for correcting that newline is just one character. Fixed in post. – Todd Partridge 'Gen2ly' Oct 31 '09 at 11:39
  • Please never delete the final newline in a file of newline-terminated lines. It screws up all kinds of things. – tchrist Jun 20 '13 at 21:41
  • 10
    @ThomasPadron-McCarthy "In computing, for every good reason there is to do something there exists a good reason not to do it and visa versa." -Jesus -- "you shouldn't do that" is a horrible answer no matter the question. The correct format is: [how to do it] but [why it *may* be bad idea]. #sacrilege – Cory Mawhorter Mar 30 '15 at 17:18
  • One reason to remove the trailing newline is if you're piping the string to somewhere else, and you can't have a trailing newline. – wisbucky Aug 27 '18 at 22:41

23 Answers23

241
perl -pe 'chomp if eof' filename >filename2

or, to edit the file in place:

perl -pi -e 'chomp if eof' filename

[Editor's note: -pi -e was originally -pie, but, as noted by several commenters and explained by @hvd, the latter doesn't work.]

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

But, in a test, it worked.

mklement0
  • 382,024
  • 64
  • 607
  • 775
pavium
  • 14,808
  • 4
  • 33
  • 50
  • 11
    You can make it safer by using `chomp`. And it beats slurping the file. – Sinan Ünür Oct 31 '09 at 11:17
  • 6
    Blasphemy though it is, it works very well. perl -i -pe 'chomp if eof' filename. Thank you. – Todd Partridge 'Gen2ly' Oct 31 '09 at 13:27
  • 14
    The funny thing about blasphemy and heresy is it's usually hated because it's correct. :) – Ether Oct 31 '09 at 17:11
  • 1
    Its not pretty but it works. Give it up for the swiss army chainsaw. – Olumide Jul 18 '12 at 13:07
  • 8
    Small correction: you can use `perl -pi -e 'chomp if eof' filename`, to edit a file in-place instead of creating a temporary file – Romuald Brunet Aug 14 '12 at 10:26
  • 8
    `perl -pie 'chomp if eof' filename` -> Can't open perl script "chomp if eof": No such file or directory; `perl -pi -e 'chomp if eof' filename` -> works – aditsu quit because SE is EVIL May 01 '13 at 01:29
  • 1
    Why is `-pie` "blasphemy," and why doesn't it behave the same as `-pi -e`? Anyone know? – Kyle Strand Mar 23 '15 at 17:55
  • 3
    @KyleStrand I don't know about the "blasphemy" part other than perhaps the mere fact of recommending perl could be considered blasphemy on an awk website, but the reason `-pie` and `-pi -e` don't work the same way is that the `-i` option takes an optional argument. `-pie` uses `e` as the argument to `-i`, specifying the backup suffix, and then interprets `'chomp if eof'` as a filename, since it isn't preceded by an `-e` option. `-pi -e` omits the argument for `-i`, and allows `-e` to be treated as an option. –  Apr 07 '15 at 11:02
  • This also turns `\r\n` to `\n` – m13r Apr 26 '17 at 11:46
  • 2
    isn't all of Perl blasphemy? ;) – pkaeding Apr 16 '18 at 00:37
  • 1
    `cat foo | perl -pe 'chomp if eof'` removes the newline from `foo`, but `git status` still reports a diff. Maybe the file was `\r\n` and `perl` just removes the `\n`? – Heath Borders Dec 03 '19 at 16:24
  • @Ether [A fun read](http://www.paulgraham.com/say.html) about that – Nathan majicvr.com Jun 23 '20 at 00:17
68

You can take advantage of the fact that shell command substitutions remove trailing newline characters:

Simple form that works in bash, ksh, zsh:

printf %s "$(< in.txt)" > out.txt

Portable (POSIX-compliant) alternative (slightly less efficient):

printf %s "$(cat in.txt)" > out.txt

Note:


A guide to the other answers:

  • If Perl is available, go for the accepted answer - it is simple and memory-efficient (doesn't read the whole input file at once).

  • Otherwise, consider ghostdog74's Awk answer - it's obscure, but also memory-efficient; a more readable equivalent (POSIX-compliant) is:

  • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt

  • Printing is delayed by one line so that the final line can be handled in the END block, where it is printed without a trailing \n due to setting the output-record separator (OFS) to an empty string.

  • If you want a verbose, but fast and robust solution that truly edits in-place (as opposed to creating a temp. file that then replaces the original), consider jrockway's Perl script.

mklement0
  • 382,024
  • 64
  • 607
  • 775
56

You can do this with head from GNU coreutils, it supports arguments that are relative to the end of the file. So to leave off the last byte use:

head -c -1

To test for an ending newline you can use tail and wc. The following example saves the result to a temporary file and subsequently overwrites the original:

if [[ $(tail -c1 file | wc -l) == 1 ]]; then
  head -c -1 file > file.tmp
  mv file.tmp file
fi

You could also use sponge from moreutils to do "in-place" editing:

[[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file

You can also make a general reusable function by stuffing this in your .bashrc file:

# Example:  remove-last-newline < multiline.txt
function remove-last-newline(){
    local file=$(mktemp)
    cat > $file
    if [[ $(tail -c1 $file | wc -l) == 1 ]]; then
        head -c -1 $file > $file.tmp
        mv $file.tmp $file
    fi
    cat $file
}

Update

As noted by KarlWilbur in the comments and used in Sorentar's answer, truncate --size=-1 can replace head -c-1 and supports in-place editing.

Thor
  • 45,082
  • 11
  • 119
  • 130
  • 4
    Best solution of all so far. Uses a standard tool that really every Linux distribution has, and is concise and clear, without any sed or perl wizardry. – Dakkaron Sep 14 '15 at 17:58
  • 3
    Nice solution. One change is that I think I'd use `truncate --size=-1` instead of `head -c -1` since it just resizes the input file rather than reading in the input file, writing it out to another file, then replacing the original with the output file. – Karl Wilbur Dec 12 '17 at 16:56
  • 4
    Note that `head -c -1` will remove the last character regardless if it is a newline or not, that's why you have to check whether the last character is a newline before you remove it. – wisbucky Aug 27 '18 at 20:59
  • 1
    Unfortunately does not work on Mac. I suspect it doesn't work on any BSD variant. – Edward Falk Mar 12 '20 at 20:03
19
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
  • 346,391
  • 90
  • 374
  • 439
  • Good original way to think about it. Thanks Dennis. – Todd Partridge 'Gen2ly' Oct 31 '09 at 13:27
  • 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 Nov 01 '09 at 01:49
  • you can make the output a pipe with process substitution: `head -n -1 abc | cat <(tail -n 1 abc | tr -d '\n') | ...` – BCoates Jan 28 '12 at 03:21
  • @BCoates: That doesn't do the same thing. Yours only gives the last line (without a newline). The OP wants the *whole* file with only the last newline removed. Your pipeline would work like this: `head -n -1 ifscomma && cat <(tail -n 1 ifscomma | tr -d '\n')` or `head -n -1 ifscomma | cat - <(tail -n 1 ifscomma | tr -d '\n')`. In the latter one, the hyphen causes `cat` to concatenate what comes across the pipe with the output of the process substitution. Otherwise, the output of `head` would be ignored. – Dennis Williamson Jan 28 '12 at 14:42
  • I forgot to edit the name of the file in my previous comment to change it from the test file I was using to the sample name "abc": `s/ifscomma/abc/g` – Dennis Williamson Jan 28 '12 at 23:20
  • This worked faster than the perl command on a 1MB file for me. Great thanks! – hese Apr 27 '12 at 15:23
  • 2
    Using -c instead of -n for head and tail should be even faster. – rudimeier Apr 18 '13 at 18:10
  • 1
    For me, head -n -1 abc removed the last actual line of the file, leaving a trailing newline; head -c -1 abc seemed to work better – ChrisV Apr 04 '14 at 11:09
  • just another variant on the "one liner" with `cat` examples show in the comments (no pipe necessary, using only process substitution, feel free to change the `head` or `tail` options as necessary): `cat <(head -n -1 abc) <(tail -n 1 abc | tr -d '\n')` – michael Oct 31 '18 at 08:06
11

gawk

awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • Still looks like a lot of characters to me... learning it slowly :). Does the job though. Thanks ghostdog. – Todd Partridge 'Gen2ly' Oct 31 '09 at 13:35
  • 1
    `awk '{ prev_line = line; line = $0; } NR > 1 { print prev_line; } END { ORS = ""; print line; }' file` this should be easier to read. – Yevhen Pavliuk May 07 '13 at 08:35
  • How about: `awk 'NR>1 {print p} {p=$0} END {printf $0}' file`. –  Nov 12 '16 at 21:58
  • @sorontar The first argument to `printf` is the _format_ argument. Thus if the input file had something that could be interpreted as a format specifier like `%d`, you'd get an error. A fix would be to change it to `printf "%s" $0` – Robin A. Meade Oct 11 '19 at 23:42
9

A fast solution is using the gnu utility truncate:

[ -z $(tail -c1 file) ] && truncate -s-1 file

The test will be true if the file does have a trailing new line.

The removal is very fast, truly in place, no new file is needed and the search is also reading from the end just one byte (tail -c1).

mwfearnley
  • 3,303
  • 2
  • 34
  • 35
  • 1
    truncate: missing file operand – Brian Hannay May 30 '18 at 15:10
  • 2
    it's just missing the trailing filename in the example, i.e., `[ -z $(tail -c1 filename) ] && truncate -s -1 filename` (also, in reply to the other comment, the `truncate` command does not work with stdin, a filename is required) – michael Oct 31 '18 at 08:17
8

A very simple method for single-line files, requiring GNU echo from coreutils:

/bin/echo -n $(cat $file)
anotheral
  • 89
  • 1
  • 4
  • This is a decent way if it's not too expensive (repetitive). –  Aug 19 '16 at 05:17
  • This has issues when `\n` is present. As it gets converted to a new line. – Chris Stryczynski Jul 19 '17 at 15:42
  • Also seems to work for multi-line files it the `$(...)` is quoted – Thor Sep 26 '17 at 16:57
  • definitely need to quote that... `/bin/echo -n "$(cat infile)"` Also, I'm not sure what the max len of `echo` or the shell would be across os/shell versions/distros (I was just googling this & it was a rabbit hole), so I'm not sure how portable (or performant) it actually would be for anything other than small files -- but for small files, great. – michael Oct 31 '18 at 08:24
7

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
  • 42,082
  • 9
  • 61
  • 86
  • 2
    but that has the disadvantage of not reseting ownership/permissions for the file...err, wait... – ysth Nov 02 '09 at 04:47
  • 1
    Verbose, but both fast and robust - seems to be the only _true_ in-place file-editing answer here (and since it may not be obvious to everyone: this is a _Perl_ script). – mklement0 Apr 08 '15 at 16:12
6

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

This modifies the file in-place, rather than making a copy of the file and stripping the newline from the last line of the copy. If the file is large, this will be much faster than the Perl solution that was chosen as the best answer.

It 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
  • 74,789
  • 21
  • 92
  • 117
5

Yet another perl WTDI:

perl -i -p0777we's/\n\z//' filename
ysth
  • 96,171
  • 6
  • 121
  • 214
3
perl -pi -e 's/\n$// if(eof)' your_file
Vijay
  • 65,327
  • 90
  • 227
  • 319
  • Effectively the same as the accepted answer, but arguably clearer in concept to non-Perl users. Note that there's no need for the `g` or the parentheses around `eof`: `perl -pi -e 's/\n$// if eof' your_file`. – mklement0 Apr 08 '15 at 15:48
3
$  perl -e 'local $/; $_ = <>; s/\n$//; print' a-text-file.txt

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

Community
  • 1
  • 1
Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
  • 1
    That takes out all the newlines. Equivalent to `tr -d '\n'` – Dennis Williamson Oct 31 '09 at 11:03
  • This works good too, probably less blasphemous than paviums's. – Todd Partridge 'Gen2ly' Oct 31 '09 at 11:47
  • 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 Nov 01 '09 at 20:45
  • in that vein, most javascript/css minifiers will remove trailing newlines, and yet produce text files. – ysth Nov 02 '09 at 04:53
  • @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 Nov 02 '09 at 10:12
2

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
  • 21
  • 1
2

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

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

It will not work on multiple newlines...

* Works only if the last line is a blank line.

Kashyap
  • 15,354
  • 13
  • 64
  • 103
  • Here's a `sed` solution that works even for a non-blank last line: https://stackoverflow.com/a/52047796 – wisbucky Aug 27 '18 at 22:38
2

This is a good solution if you need it to work with pipes/redirection instead of reading/output from or to a file. This works with single or multiple lines. It works whether there is a trailing newline or not.

# with trailing newline
echo -en 'foo\nbar\n' | sed '$s/$//' | head -c -1

# still works without trailing newline
echo -en 'foo\nbar' | sed '$s/$//' | head -c -1

# read from a file
sed '$s/$//' myfile.txt | head -c -1

Details:

  • head -c -1 truncates the last character of the string, regardless of what the character is. So if the string does not end with a newline, then you would be losing a character.
  • So to address that problem, we add another command that will add a trailing newline if there isn't one: sed '$s/$//' . The first $ means only apply the command to the last line. s/$// means substitute the "end of the line" with "nothing", which is basically doing nothing. But it has a side effect of adding a trailing newline is there isn't one.

Note: Mac's default head does not support the -c option. You can do brew install coreutils and use ghead instead.

wisbucky
  • 33,218
  • 10
  • 150
  • 101
1

Yet another answer FTR (and my favourite!): echo/cat the thing you want to strip and capture the output through backticks. The final newline will be stripped. For example:

# Sadly, outputs newline, and we have to feed the newline to sed to be portable
echo thingy | sed -e 's/thing/sill/'

# No newline! Happy.
out=`echo thingy | sed -e 's/thing/sill/'`
printf %s "$out"

# Similarly for files:
file=`cat file_ending_in_newline`
printf %s "$file" > file_no_newline
Nicholas Wilson
  • 9,435
  • 1
  • 41
  • 80
  • 1
    I found the cat-printf combo out by accident (was trying to get the opposite behavior). Note that this will remove _ALL_ trailing newlines, not just the last. – technosaurus Sep 27 '13 at 20:50
1

ruby:

ruby -ne 'print $stdin.eof ? $_.strip : $_'

or:

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
peak
  • 105,803
  • 17
  • 152
  • 177
1

POSIX SED:

'${/^$/d}'

$ - match last line


{ COMMANDS } - A group of commands may be enclosed between { and } characters. This is particularly useful when you want a group of commands to be triggered by a single address (or address-range) match.
Oleg Mazko
  • 1,720
  • 15
  • 10
  • I think this will only remove it if the last line is blank. It will not remove the trailing newline if the last line is not blank. For example, `echo -en 'a\nb\n' | sed '${/^$/d}'` will not remove anything. `echo -en 'a\nb\n\n' | sed '${/^$/d}'` will remove since the entire last line is blank. – wisbucky Aug 27 '18 at 21:16
0

I had a similar problem, but was working with a windows file and need to keep those CRLF -- my solution on linux:

sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked
cadrian
  • 7,332
  • 2
  • 33
  • 42
0

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
  • 43,965
  • 20
  • 101
  • 140
0
sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile

Should remove any last occurence of \n in file. Not working on huge file (due to sed buffer limitation)

NeronLeVelu
  • 9,908
  • 1
  • 23
  • 43
0
sed ':a;/^\n*$/{$d;N;};/\n$/ba' file
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
0

Here's a simple solution that uses sed. Your versions of sed needs to support the -z option.

       -z, --null-data

              separate lines by NUL characters

It can either be used in a pipe or used to edit the file in place with the -i option

sed -ze 's/\n$//' file
lenny
  • 1