Delete all lines before n lines above the line containing the first match

If a file contains the following:

Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9

How to delete the lines above two lines before "Line 5", so that the file will now contain only the following?

Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Asked By: Sadi

||

Such problems are easier to solve by first reversing the input line wise. For example:

$ tac ip.txt | awk '1; /Line 5/{n=3} n && !--n{exit}' | tac
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9

n && !--n{exit} will become true when n reaches 0. In this case, that means the line containing Line 5 followed by two more lines.

With sed:

tac ip.txt | sed '/Line 5/{n; n; q}' | tac

Note: The above solutions won’t work if there are multiple matches, as it would match the last occurrence in the file. Here’s one workaround:

awk '!f && /Line 5/{print p2 ORS p1; f=1} f; {p2=p1; p1=$0}' ip.txt
Answered By: Sundeep

This may be simpler using ed – which supports address offsets:

printf '%sn' '/Line 5/-2,$p' | ed -s file

or (modifying the file in place)

printf '%sn' '1,/Line 5/-3d' 'wq' | ed -s file
Answered By: steeldriver

If you have a file with fewer than 10,000 lines you could use this

grep -B2 -A9999 'test test' file
Answered By: Chris Davies

Using pcregrep:

$ pcregrep -M "(.*n){2}.*Line 5(.*n)*" file

Using awk:

$ awk -v var=2 '/Line 5/{n=NR; for(i=n-var;i<n;) print ar[i++]}
!n{ar[NR]=$0}n'
Answered By: Prabhjot Singh

Using any awk with a 2-pass approach to read just 1 line at a time into memory so it’ll work no matter how large your input file is (but won’t work for piped input as we can’t open that twice):

$ awk 'NR==FNR{ if (/Line 5/) {n=NR-3; nextfile}; next } FNR>n' file file
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9

If the version of awk you’re running doesn’t support nextfile it’ll still work but just run a bit slower than if it did support nextfile.

Answered By: Ed Morton

This should work with any :

#!/usr/bin/awk -f
BEGIN { keep = 2 ; find = "Line 5" }

$0 == find {
        for ( i = 1; i < NR; i++ )
                if ( ( i + keep ) >= NR ) print m[i]
        print ; f++ ; next
}

!f { m[NR] = $0 ; next }

1

Save it in a file called, say, exec.awk and call it as: awk -f exec.awk data

You can edit the boundaries, and the match keyword, by editing keep and find variables.

Answered By: DanieleGrassini
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.