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
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
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
If you have a file with fewer than 10,000 lines you could use this
grep -B2 -A9999 'test test' file
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'
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
.
This should work with any awk:
#!/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.