100

Say I have some arbitrary multi-line text file:

sometext
moretext
lastline

How can I remove only the last character (the e, not the newline or null) of the file without making the text file invalid?

2
  • What you have done to solve this? -1 Commented Dec 5, 2014 at 7:06
  • 7
    Listing a bunch of garbage sed and awk commands that strip the last character off of every line didn't feel terribly constructive. Heh, knew I was going to get dinged for that one. Still, couldn't bring myself to leave in the sentence "I tried a bunch of sed and awk, but could only strip out every line's last char in a variety of ways". Commented Dec 5, 2014 at 16:32

10 Answers 10

137

A simpler approach (outputs to stdout, doesn't update the input file):

sed '$ s/.$//' somefile
  • $ is a Sed address that matches the last input line only, thus causing the following function call (s/.$//) to be executed on the last line only.

  • s/.$// replaces the last character on the (in this case last) line with an empty string; i.e., effectively removes the last char. (before the newline) on the line.

    • Note: The command is a no-op if the file ends in two or more newlines.

. matches any character on the line, and following it with $ anchors the match to the end of the line; note how the use of $ in this regular expression is conceptually related, but technically distinct from the previous use of $ as a Sed address.

  • Example with stdin input (assumes Bash, Ksh, or Zsh):

      $ sed '$ s/.$//' <<< $'line one\nline two'
      line one
      line tw
    

To update the input file too (do not use if the input file is a symlink):

sed -i '$ s/.$//' somefile

Note:

  • On macOS, you'd have to use -i '' instead of just -i; for an overview of the pitfalls associated with -i, see the bottom half of this answer.
  • If you need to process very large input files and/or performance / disk usage are a concern and you're using GNU utilities (Linux), see ImHere's helpful answer.
Sign up to request clarification or add additional context in comments.

1 Comment

@curiousity, no, but fails if there are two or more newlines at the end of the file; I've updated the answer to make that clear.
98

truncate

truncate -s-1 file

Removes one (-1) character from the end of the same file. Exactly as a >> will append to the same file.

The problem with this approach is that it doesn't retain a trailing newline if it existed.

The solution is:

if     [ -n "$(tail -c1 file)" ]    # if the file has not a trailing new line.
then
       truncate -s-1 file           # remove one char as the question request.
else
       truncate -s-2 file           # remove the last two characters
       echo "" >> file              # add the trailing new line back
fi

This works because tail takes the last byte (not char).

It takes almost no time even with big files.

Why not sed

The problem with a sed solution like sed '$ s/.$//' file is that it reads the whole file first (taking a long time with large files), then you need a temporary file (of the same size as the original):

sed '$ s/.$//' file  > tempfile
rm file; mv tempfile file

And then move the tempfile to replace the file.

2 Comments

On Mac you should brew install truncate first and then truncate -s -1 file
This is very nice solution, however for some reason the truncate does not work when placed in a bash script.
4

Here's another using ex, which I find not as cryptic as the sed solution:

 printf '%s\n' '$' 's/.$//' wq | ex somefile

The $ goes to the last line, the s deletes the last character, and wq is the well known (to vi users) write+quit.

Comments

2

After a whole bunch of playing around with different strategies (and avoiding sed -i or perl), the best way i found to do this was with:

sed '$! { P; D; }; s/.$//' somefile

1 Comment

Not sure why you avoid sed -i. This is just a function to write data back to the file. Now you only get output to monitor.
2

If the goal is to remove the last character in the last line, this awk should do:

awk '{a[NR]=$0} END {for (i=1;i<NR;i++) print a[i];sub(/.$/,"",a[NR]);print a[NR]}' file
sometext
moretext
lastlin

It store all data into an array, then print it out and change last line.

2 Comments

I didn't have luck trying to > to the same file. Would recommend outputting to new file, deleting old file, then doing a filename change if necessary.
@MayTheSForceBeWithYou Gnu Awk 4.1 and newer has inline edit, so try awk -i 'your code' file
2

Just a remark: sed will temporarily remove the file. So if you are tailing the file, you'll get a "No such file or directory" warning until you reissue the tail command.

Comments

1

EDITED ANSWER

I created a script and put your text inside on my Desktop. this test file is saved as "old_file.txt"

sometext
moretext
lastline

Afterwards I wrote a small script to take the old file and eliminate the last character in the last line

#!/bin/bash
no_of_new_line_characters=`wc  '/root/Desktop/old_file.txt'|cut -d ' ' -f2`
let "no_of_lines=no_of_new_line_characters+1"
sed -n 1,"$no_of_new_line_characters"p  '/root/Desktop/old_file.txt' > '/root/Desktop/my_new_file'
sed -n "$no_of_lines","$no_of_lines"p '/root/Desktop/old_file.txt'|sed 's/.$//g' >> '/root/Desktop/my_new_file'

opening the new_file I created, showed the output as follows:

sometext
moretext
lastlin

I apologize for my previous answer (wasn't reading carefully)

2 Comments

It's asking to remove only the last char of a file, not the last char of each line.
I apologize for my answer wasn't reading carefully..I was able to get the solution using a small script I created. Hope this helps MaxPRafferty
0

sed 's/.$//' filename | tee newFilename

This should do your job.

2 Comments

That will remove the last character from every line, not just the last character of the last line.
@MaxPRafferty my bad. I thought the question was to remove the last character of every line
0

A couple perl solutions, for comparison/reference:

(echo 1a; echo 2b) | perl -e '$_=join("",<>); s/.$//; print'

(echo 1a; echo 2b) | perl -e 'while(<>){ if(eof) {s/.$//}; print }'

I find the first read-whole-file-into-memory approach can be generally quite useful (less so for this particular problem). You can now do regex's which span multiple lines, for example to combine every 3 lines of a certain format into 1 summary line.

For this problem, truncate would be faster and the sed version is shorter to type. Note that truncate requires a file to operate on, not a stream. Normally I find sed to lack the power of perl and I much prefer the extended-regex / perl-regex syntax. But this problem has a nice sed solution.

Comments

0

I think the most Unix solution is to use 'head'

head -c-1 infile > outfile

2 Comments

I like the look of this solution but this gives me head: illegal byte count -- -1 on OSX - I suspect it works great on proper GNU tools
Works on linux (gnu coreutils 9.4)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.