93

I use sed to quickly delete lines with specific position as

sed '1d'
sed '5d'

But, what if I want to delete the last line of the file and I don't know the count of lines (I know I can get that using wc and several other tricks).

Currently, using a workaround with head and tailcombined with wc to do so. Any quick twists here?

1
  • @Nils I am on 'Red Hat Enterprise Linux Server release 5.5 (Tikanga)'. Can you point for the options for head and tail you know of on different flavours? Commented Nov 19, 2012 at 6:39

9 Answers 9

162

in sed $ is the last line so to delete the last line:

sed '$d' <file>

Updated to include a way to delete multiple lines from the bottom of the file:

tac <file> | tail -n +3 | tac > <new_file>
  • tac reads the file backwards (cat backwards)
  • tail -n +3 reads the input starting at the nth line
  • tac reads the input and reverses the order back to the original
3
  • 48
    Use sed -i '$d' <file> to edit file in place. Commented May 22, 2017 at 18:59
  • 2
    somehow your answer didn't work without the top comment's suggestion i.e. without the -i. Why is it? i.e. sed -i '$d' <file> is needed for me. Commented Apr 24, 2021 at 0:31
  • Saved my LIFE! 😭😭 Commented Aug 24, 2023 at 19:07
43

$ for the last line:

sed '$d' file
9
  • 1
    Sadly though one has to use more complex techniques to delete, say, the last two lines. Commented Oct 24, 2012 at 15:13
  • 1
    What do you mean? sed '$d' file; sed '$d' file Commented Oct 24, 2012 at 15:35
  • 5
    @Rob: sed '$d' file doesn't actually modify the file; it just prints out the contents of the file, minus the last line. So sed '$d' file; sed '$d' file will print out the contents of the file twice, minus the last line each time. The delete-the-last-two-lines equivalent of sed '$d' file is sed '$d' file | sed '$d'. Commented Oct 24, 2012 at 18:48
  • That's what I had originally but changed it because I'm at work on a window box and couldn't check. Commented Oct 24, 2012 at 19:45
  • 2
    Yeah, that's inelegant but of course it works. I would do it in one pass with sed -n '1{h;n;}; $q; x; p;'. I was pouting that we can't just do sed '$-1,$d'. Commented Oct 24, 2012 at 20:24
16

cat file.txt | head -n -1 > new_file.txt

Beware, it seems, depending on the last line of file.txt (if it ends with EOF, or \n and then EOF), the number of lines in new_file.txt may be the same (as file.txt) after this command (this happens when there is no \n) - in any case, the contents of the last line is deleted.

Also, note that you must use a second (intermediate) file. If you cat and redirect to the same file, you'll erase it.

4
  • 8
    Where this works, it could also be simply head -n -1 file.txt > new_file.txt. Note though that negative counts on head -n is only available for some implementations of head. For example, it doesn't work for FreeBSD's or for the BusyBox version of head. Commented Oct 25, 2012 at 9:50
  • 1
    @dubiousjim: Agreed, head -n -1 file.txt > new_file.txt is better. It works the same way with respect to my two comments (same number of lines, and, erase on same file). As for the negative argument, such differences occur from time to time between Unixes, and they are often frustrating because you expect it to be the same command entirely (and why wouldn't you - same name and purpose!) -- anyhow, good point. (I use Debian.) Commented Oct 25, 2012 at 17:38
  • head -n -1 doesn't seem to work in zsh. "head: illegal line count -- -1" Commented Mar 8, 2022 at 4:02
  • 2
    @JohnJiang It's not the shell that matters, it's the implementation of head. You are most likely not on a GNU system (possibly macOS?). Commented Jan 24, 2023 at 16:18
7

head --lines=-1. I first stumbled across this possibility in the man-page for head on a SLES11SP2-system (coreutils-8.12-6.23.1)

tail and head are part of the coreutils-rpm (at least for rpm-based-systems).

According to the changelog of coreutils, this syntax is supported since coreutils-version 5.0.1

Bad news: According to the RH5-man-page this option is not described

Good news: It works with RH5 (so in your case: it works - at least with a current version of RH5).

rpm -q coreutils shows me (on CentOS 5.8): coreutils-5.97-34.el5_8.1

I am not sure if RH5.5. already has the coreutils-version that supports it. But 5.5 has EoLed anyway.

0
7

Linux

$ is the last line, d for delete:

sed '$d' ~/path/to/your/file/name

MacOS

Equivalent of the sed -i

sed -i '' -e '$ d' ~/path/to/your/file/name
3

On Mac (BSD head/tail) you can use:

cat file.txt | tail -r | tail -n +2 | tail -r
1
perl -ne 'print unless eof' file

Or with some head implementations:

head -n -1 file

Or:

sed '$d' file

Outputs the contents of the file except the last line (with for the latter variation in behaviour between sed implementations if that last line is not delimited or the input contains NUL bytes or sequences of bytes that can't be decoded into characters in the user's locale or overlong lines).

With perl and some sed implementations, you can add a -i option (or -i '' in some sed implementations) for the file to be edited in-place, but what happens then is that it creates a new file with that same output as contents to replace the original file.

To remove the last line of a file and modify it in place, it should be enough to truncate it just after the newline character that precedes that line.

On a GNU system, that can be done with:

n=1 file=path/to/some/file
truncate -s "-$(tail -n "$n" < "$file" | wc -c)" -- "$file"

Which truncates $file by an amount that corresponds to the length in bytes of the last $n lines.

tail does not read the full file contents, it just seeks to the end and reads backward until it finds the start of those $n lines, so even on a 5TiB file, as long as lines are not overly long, that will be close to instantaneous, while perl -i or sed -i would need to write 5TiB worth of extra data to disk.

The ksh93 shell has builtin seek (<#((offset))) and truncate (<>;) operators, so with that shell, you can also do:

0<>; "$file" <#(( EOF - ${ tail -n "$n" < "$file" | wc -c; } ))

Where 0<>; "$file" opens $file in read+write mode (like in Bourne's <> direction operator) but with truncation at current offset at the end (if the command being redirected is successful, but here there's no command), and <#(( EOF - offset )) seeks to an offset relative to the end (EOF). ${ ...; } is a form of command substitution that skips creating a subshell.

0

⚠️ This is hackish, brutish, ...-ish

If it is a small file, why not? Iterate it. 😅 Gets the job done.

Delete n lines from the end of a file:

for n in {1..5}; do sed -i '$d' /path/to/file; done
0

Using Raku (formerly known as Perl_6)

...identifying eof:

~$ raku -e 'my $fh = @*ARGS.IO.open; for $fh.lines { .put unless $fh.eof };'  file

OR:

~$ raku -e 'my $fh = @*ARGS.IO.open; .put unless $fh.eof for $fh.lines;'  file

Raku attempts to regularize some coding constructs as compared to Perl. While -pe flags will give sed-like behavior and -ne flags will give awk-like behavior, testing for file-level information in Raku (e.g. eof) puts us in the realm of filehandles.

Thus the simplest thing to do (starting from the -e flag) is to first use the @*ARGS dynamic variable to locate arguments (filenames) placed on the command line, assign to a $fh filehandle, and open it. Then reading the filehandle using lines, output all lines unless eof (the last line). Looping with for is necessary because Raku auto-chomps (can be turned off).


... or using Raku's head:

~$ raku -e 'my $fh = @*ARGS.IO.open; .put for $fh.lines>>.head[0..*-2] ;'  file

Raku can perform head and tail operations as well*, and a more efficient approach may be to read the file using lines then head the resulting anonymous array, leaving off the last element. In (zero-indexed) Raku file lines are numbered 0 to *-1, so dropping the last line of the file requires you to write .head[0..*-2]. Here we must remember to >> hyper into the anonymous array, to pull out the desired index numbers.

The first link below gives an example of grepping through an enormous file efficiently and may be relevant to the task at hand.


*Unlike Perl, Raku does not have a truncate command, which mught be a useful command to add.

https://docs.raku.org/language/io-guide
https://docs.raku.org
https://raku.org

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.