204

I want to remove some n lines from the end of a file. Can this be done using sed?

For example, to remove lines from 2 to 4, I can use

$ sed '2,4d' file

But I don't know the line numbers. I can delete the last line using

$sed $d file

but I want to know the way to remove n lines from the end. Please let me know how to do that using sed or some other method.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
mezda
  • 3,537
  • 6
  • 30
  • 37

26 Answers26

281

I don't know about sed, but it can be done with head:

head -n -2 myfile.txt
ams
  • 24,923
  • 4
  • 54
  • 75
  • 29
    +1 for simplicity. The equivalent sed command is butt ugly: `sed -e :a -e '$d;N;2,5ba' -e 'P;D' file` (`,5` for last 5 lines). – Marc B Nov 14 '12 at 14:28
  • 76
    Note that this works with some versions of `head`, but is not standard. Indeed, the standard for `head` states: `The application shall ensure that the number option-argument is a positive decimal integer.` – William Pursell Nov 14 '12 at 15:51
  • 1
    @rjdkolb thanks for your edit, but negative numbers traditionally do have a dash in front. – ams Feb 21 '14 at 22:43
  • 1
    you have to pipe it into `tee` if you want to update the input file – GM-Script-Writer-62850 Jan 04 '15 at 01:18
  • That sounds unsafe; are you sure there's no race condition there? I'd imagine it would vary from shell to shell. – ams Jan 04 '15 at 09:29
  • 33
    To add to @WilliamPursell's answer... On Mac OS default shell, this does not work. – JDS Jan 08 '15 at 21:11
  • 13
    Nor on BSD, but the question is tagged Linux. – ams Jan 08 '15 at 21:50
  • @ams doesn't head just print to standard output? tested Ubuntu 14.0.4 – codecowboy May 17 '15 at 09:27
  • 1
    @codecowboy, yes, just like the sed commands in the question. You can pipe it to a new file. There's no modify-in-place option here. (Don't try to pipe it back to itself!) – ams May 18 '15 at 10:24
  • 10
    minus 1, because, in addition to osx, this isn't working for me on Ubuntu14 - `head: illegal line count -- -2` – Michael Durrant Aug 18 '15 at 09:40
  • 1
    @MichaelDurrant Works fine on 14.04.2 for me. Did you install `head` from something other that GNU coreutils? Try `head --version`. – ams Aug 18 '15 at 10:12
  • For Mac and BSD folks, here's how I solved it: `some_command | tail -r | sed -e '1,3d' | tail -r`. Note `tail -r` to reverse the buffer the `sed` command deletes the first 3 lines, and `tail -r` reverses the buffer again to put it back to the original order. – berto Feb 15 '19 at 14:02
  • 5
    It being 2019 now, I felt inspired to use GNU head on OSX in the most overkill possible way— by using Docker: `docker run -v /Users/me/reports:/reports ubuntu head -n -7 /reports/ugly_footer.csv` – Alan Gerber Apr 11 '19 at 23:32
  • 4
    This line works with `ghead` in OSX. Install with `brew install coreutils` – n-a-t-e Jan 22 '20 at 18:18
42

From the sed one-liners:

# delete the last 10 lines of a file
sed -e :a -e '$d;N;2,10ba' -e 'P;D'   # method 1
sed -n -e :a -e '1,10!{P;N;D;};N;ba'  # method 2

Seems to be what you are looking for.

qstebom
  • 719
  • 4
  • 12
  • @Thor you can change how many lines are removed from the file by changing the `10` in `'$d;N;2,10ba'` – Alexej Magura Oct 28 '16 at 16:48
  • 1
    @AlexejMagura: I was commenting on an earlier version of the answer. – Thor Oct 29 '16 at 13:27
  • 1
    Sry, I'm pretty new to sed command and this looks really confusing to me. How do I specify the file to apply against with this command? – Alex Mar 17 '21 at 14:54
  • 1
    @Alex you specify the file by appending the file name after those commands e.g. `sed -e :a -e '$d;N;2,10ba' -e 'P;D' filename` – EarthIsHome Apr 30 '23 at 18:35
42

If hardcoding n is an option, you can use sequential calls to sed. For instance, to delete the last three lines, delete the last one line thrice:

sed '$d' file | sed '$d' | sed '$d'
Kyle
  • 564
  • 4
  • 4
31

Use sed, but let the shell do the math, with the goal being to use the d command by giving a range (to remove the last 23 lines):

sed -i "$(($(wc -l < file)-22)),\$d" file

To remove the last 3 lines, from inside out:

$(wc -l < file)

Gives the number of lines of the file: say 2196

We want to remove the last 23 lines, so for left side or range:

$((2196-22))

Gives: 2174 Thus the original sed after shell interpretation is:

sed -i '2174,$d' file

With -i doing inplace edit, file is now 2173 lines!

If you want to save it into a new file, the code is:

sed -i '2174,$d' file > outputfile
Santosa Sandy
  • 217
  • 6
  • 20
lzc
  • 919
  • 7
  • 16
  • This is the right answer (sed one-liner). Let's make it a bit easier with three lines: `file=myfile.txt` `n=23` `sed -i "$(($(wc -l < $file)-n+1)),\$d" $file` – André Chalella Apr 10 '23 at 00:43
28

A funny & simple sed and tac solution :

n=4
tac file.txt | sed "1,$n{d}" | tac

NOTE

  • double quotes " are needed for the shell to evaluate the $n variable in sed command. In single quotes, no interpolate will be performed.
  • tac is a cat reversed, see man 1 tac
  • the {} in sed are there to separate $n & d (if not, the shell try to interpolate non existent $nd variable)
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
22

You could use head for this.

Use

$ head --lines=-N file > new_file

where N is the number of lines you want to remove from the file.

The contents of the original file minus the last N lines are now in new_file

richard
  • 309
  • 2
  • 8
13

Just for completeness I would like to add my solution. I ended up doing this with the standard ed:

ed -s sometextfile <<< $'-2,$d\nwq'

This deletes the last 2 lines using in-place editing (although it does use a temporary file in /tmp !!)

Michel
  • 1,395
  • 13
  • 14
8

To truncate very large files truly in-place we have truncate command. It doesn't know about lines, but tail + wc can convert lines to bytes:

file=bigone.log
lines=3
truncate -s -$(tail -$lines $file | wc -c) $file

There is an obvious race condition if the file is written at the same time. In this case it may be better to use head - it counts bytes from the beginning of file (mind disk IO), so we will always truncate on line boundary (possibly more lines than expected if file is actively written):

truncate -s $(head -n -$lines $file | wc -c) $file

Handy one-liner if you fail login attempt putting password in place of username:

truncate -s $(head -n -5 /var/log/secure | wc -c) /var/log/secure
Kamil Christ
  • 324
  • 4
  • 4
5

This might work for you (GNU sed):

sed ':a;$!N;1,4ba;P;$d;D' file
potong
  • 55,640
  • 6
  • 51
  • 83
  • Nice. You missed the ending apostrophe (`'`). – Thor Nov 15 '12 at 08:20
  • sorry for so late comment but i try and (adpated for my AIX/posix) it failed printing all but last line. Reading the code, i don't understand how the last four lines are removed, the explicit loop is on 4 first lines so it certainly loop after a `d`, `D` or `P` with GNU sed and not on posix version. Seems lines are not printed after the `D` and keep in working buffer for a new loop without passing to 'end' of actions line. – NeronLeVelu Jan 22 '15 at 09:21
  • @NeronLeVelu the program reads in a window of 4 lines into the pattern space and then appends the next line and prints the first until it reaches the end of file where it deletes the remaining lines. – potong Jan 22 '15 at 13:50
  • @potong yes, i test on a GNU sed and it keep the buffer between lines where posix unload it after the `D` making the opposite effect. – NeronLeVelu Jan 22 '15 at 14:02
5

Most of the above answers seem to require GNU commands/extensions:

    $ head -n -2 myfile.txt
    -2: Badly formed number

For a slightly more portible solution:

     perl -ne 'push(@fifo,$_);print shift(@fifo) if @fifo > 10;'

OR

     perl -ne 'push(@buf,$_);END{print @buf[0 ... $#buf-10]}'

OR

     awk '{buf[NR-1]=$0;}END{ for ( i=0; i < (NR-10); i++){ print buf[i];} }'

Where "10" is "n".

2

With the answers here you'd have already learnt that sed is not the best tool for this application.

However I do think there is a way to do this in using sed; the idea is to append N lines to hold space untill you are able read without hitting EOF. When EOF is hit, print the contents of hold space and quit.

sed -e '$!{N;N;N;N;N;N;H;}' -e x

The sed command above will omit last 5 lines.

Rishabh Sagar
  • 1,744
  • 2
  • 17
  • 27
2

It can be done in 3 steps:

a) Count the number of lines in the file you want to edit:

 n=`cat myfile |wc -l`

b) Subtract from that number the number of lines to delete:

 x=$((n-3))

c) Tell sed to delete from that line number ($x) to the end:

 sed "$x,\$d" myfile
Bertrand Martel
  • 42,756
  • 16
  • 135
  • 159
  • Bad math. If you must delete 3 lines, then subtract 3 but ADD 1. With your math, if the file has 10 lines, 10 - 3 = 7 and you are using `sed` to delete lines 7 through 10, that is 4 lines, not 3. –  Feb 16 '18 at 15:06
1

You can get the total count of lines with wc -l <file> and use

head -n <total lines - lines to remove> <file>

SWW13
  • 63
  • 6
1

Try the following command:

n = line number
tail -r file_name | sed '1,nd' | tail -r
kenorb
  • 155,785
  • 88
  • 678
  • 743
  • Please explain how this works. FYI - to initialize shell variables, we shouldn't have a space around `=`. – codeforester Feb 10 '18 at 04:28
  • @codeforester First line was a comment I guess. He just uses `tail -r` where others have used `tac` to reverse lines order, and make the last `n` lines become the `n` first lines. – Nicolas Melay Mar 07 '18 at 17:46
1

This will remove the last 3 lines from file:

for i in $(seq 1 3); do sed -i '$d' file; done;

wpercy
  • 9,636
  • 4
  • 33
  • 45
  • 2
    This is way too expensive for large files -- the entire file has to be read and rewritten n times! And as many forks for `sed`. – codeforester Feb 10 '18 at 04:26
  • while this may not be the most efficient solution, it is be far the most straightforward and easy to understand – Dharam Apr 22 '20 at 18:01
  • Consider piping the sed command n-1 times into the next as described [here ⬆️](/questions/13380607/how-to-use-sed-to-remove-the-last-n-lines-of-a-file#answer-24298204) to avoid readingthe file n times. – cachius Sep 15 '22 at 15:11
0

I prefer this solution;

head -$(gcalctool -s $(cat file | wc -l)-N) file

where N is the number of lines to remove.

Martin Tournoij
  • 26,737
  • 24
  • 105
  • 146
p014k
  • 11
0

To delete last 4 lines:

$ nl -b a file | sort -k1,1nr | sed '1, 4 d' | sort -k1,1n | sed 's/^ *[0-9]*\t//'   
  • a bit heavy (especially in resources) compare to a head. A least `tac file | sed '1,4d' | tac` because sort then remove prefix cost lot of ressources. – NeronLeVelu Jan 22 '15 at 09:31
  • @NeronLeVelu you are right but there is no `tac` command is some systems (for example FreeBSD?) – mstafreshi Jan 23 '15 at 10:30
0
sed -n ':pre
1,4 {N;b pre
    }
:cycle
$!{P;N;D;b cycle
  }' YourFile

posix version

NeronLeVelu
  • 9,908
  • 1
  • 23
  • 43
0

I came up with this, where n is the number of lines you want to delete:

count=`wc -l file`
lines=`expr "$count" - n`
head -n "$lines" file > temp.txt
mv temp.txt file
rm -f temp.txt

It's a little roundabout, but I think it's easy to follow.

  1. Count up the number of lines in the main file
  2. Subtract the number of lines you want to remove from the count
  3. Print out the number of lines you want to keep and store in a temp file
  4. Replace the main file with the temp file
  5. Remove the temp file
0

In docker, this worked for me:

head --lines=-N file_path > file_path
JRichardsz
  • 14,356
  • 6
  • 59
  • 94
  • Are you sure it worked? With >> you're appending content to the end of file... – Zac Mar 17 '22 at 11:22
  • I'm sorry but this also can't work in that way if you read and write to the same file. See https://stackoverflow.com/questions/6696842/how-can-i-use-a-file-in-a-command-and-redirect-output-to-the-same-file-without-t – Zac Mar 17 '22 at 16:02
0
#!/bin/sh

echo 'Enter the file name : '
read filename

echo 'Enter the number of lines from the end that needs to be deleted :'
read n

#Subtracting from the line number to get the nth line
m=`expr $n - 1`

# Calculate length of the file
len=`cat $filename|wc -l`

#Calculate the lines that must remain
lennew=`expr $len - $m`

sed "$lennew,$ d" $filename
Doj
  • 1,244
  • 6
  • 13
  • 19
0

A solution similar to https://stackoverflow.com/a/24298204/1221137 but with editing in place and not hardcoded number of lines:

n=4
seq $n | xargs -i sed -i -e '$d' my_file
Zac
  • 2,180
  • 2
  • 23
  • 36
0

Say you have several lines:

    $ cat <<EOF > 20lines.txt
> 1
> 2
> 3
[snip]
> 18
> 19
> 20
> EOF

Then you can grab:

# leave last 15 out
$ head -n5 20lines.txt
1
2
3
4
5

# skip first 14
$ tail -n +15 20lines.txt
15
16
17
18
19
20
user2928048
  • 3,940
  • 2
  • 12
  • 12
  • Oops!! Duplicate: https://stackoverflow.com/questions/83329/how-can-i-extract-a-predetermined-range-of-lines-from-a-text-file-on-unix – user2928048 Apr 19 '22 at 12:43
0

POSIX compliant solution using ex / vi, in the vein of @Michel's solution above. @Michel's ed example uses "not-POSIX" Here-Strings. Increment the $-1 to remove n lines to the EOF ($), or just feed the lines you want to (d)elete. You could use ex to count line numbers or do any other Unix stuff.

Given the file:

cat > sometextfile <<EOF
one
two
three
four
five
EOF

Executing:

ex -s sometextfile <<'EOF'
$-1,$d
%p
wq!
EOF

Returns:

one
two
three

This uses POSIX Here-Docs so it is really easy to modify - especially using set -o vi with a POSIX /bin/sh. While on the subject, the "ex personality" of "vim" should be fine, but YMMV.

Kajukenbo
  • 109
  • 1
  • 4
-1

For deleting the last N lines of a file, you can use the same concept of

$ sed '2,4d' file

You can use a combo with tail command to reverse the file: if N is 5

$ tail -r file | sed '1,5d' file | tail -r > file

And this way runs also where head -n -5 file command doesn't run (like on a mac!).

-2

This will remove the last 12 lines

sed -n -e :a -e '1,10!{P;N;D;};N;ba'
R. Kumar
  • 103
  • 5