how to remove two lines above after we find a match in a file in BASH?

1.8k Views Asked by At

I have a file that contain data like:

  1. 123
  2. 456
  3. 789

I want to delete the above two lines (123 and 456) when I find a match 789. Is it possible to do it with sed or awk? Please Help

5

There are 5 best solutions below

1
On BEST ANSWER

Using ed, the standard editor:

ed -s file <<< $'3,$g/789/-2,.d\nw'

ed will see these commands:

3,$g/789/-2,.d
w

Explanation:

3,$g/789/

will mark all lines from line 3* to the end of the file that match /789/; then, for each marked line, it will execute the command:

-2,.d

which means: delete the range -2,., i.e., the range that starts two lines above the current one (-2) and ends here (.).

Then w means to write the file. If you want to try it first without writing to the file, but instead print on the terminal (for testing purposes), replace the w command by ,p and Q as so:

ed -s file <<< $'3,$g/789/-2,.d\n,p\nQ'

Note. ed really edits the file (it's an editor), so it will happily preserve symlinks and permissions, unlike all other methods that rely on tools that are good for streams (sed and awk).

Note. If your pattern appears in the first two lines, it will be preserved (though, this can be handled by adding some extra commands); if your pattern appears within a range of 2 lines, you might have some surprises, e.g.,

000
123
456
789
123
789

will yield an empty file, and

123
456
789
123
789

you'll get an error.


*The range starts at line 3, just to prevent errors in case your pattern appears in the first two lines

0
On

awk to the rescue!

awk '{a[NR]=$0} END{for(i=1;i<=NR;i++) if(a[i+1] a[i+2]!~/789/) print a[i]}' test.1

input (test.1)

1. 123
2. 456
3. 789
4. 012
5. 345
6. 678
7. 901
8. 789
9. 111

output

3. 789
4. 012
5. 345
8. 789
9. 111
5
On

loading the file in memory (so not the best for huge file)

sed '1h;1!H;$!d
     x;/\n[0-9]\{1,\}\.[[:space:]]\{1,\}789$/ s/\(\(\n\)[[:alnum:][:blank:][:punct:]]*\)\{3\}$/\2789/
    ' YourFile

load the file in memory, check if last line match, if yes remove 2 previous line, print result

0
On

This might work for you (GNU sed):

sed ':a;N;s/\n/&/2;Ta;/\n789$/s/.*\n//;P;D' file

Keep a moving window of 3 lines in the pattern space and if the third line is the desired pattern, remove the first two lines.

1
On

This script does not load the whole file into memory, unlike some of the other answers, so it's efficient for big files, and this assumes that you have at least 3 lines

#n
1{
    N
    h
    n
    :loop
    ${      
        /789/! {
               x
               p
               g                   
        }
        p
    }
    $!{
        H
        g
        P
        s/^[^\n]*\n//
        h
        n
        b loop
    }
}

If you save this as s.sed, you can run

sed -f s.sed file

And it will delete the two lines before the last line if the last line matches 789.

Input:

123
456
789

Output:

789

Input:

abc
123
456
789

Output:

abc
789

Input:

123
456
abc

Output:

123
456
abc

Explanation

The #n suppresses normal output. On the first line, matched by 1, we append the next line with N and copy this to the hold space with h. Then we go to the next line with n and then start the :loop.

If the current line is the last line, which is picked up by $, then we check if it doesn't match 789, in which case we swap the pattern and hold spaces with x, print out the new pattern space with p, then use g to copy the hold space onto the pattern space. Finally we print out the last line.

If the current line isn't the last line. We append the current line to the hold space with H, then we copy the hold space to pattern space. We print the first line of the pattern space with P, and then remove the first line with s/^[^\n]*\n//. We copy this back to the hold space, go to the next line, and repeat the loop with b loop.