Jeremy Stender
Jeremy Stender

Reputation: 51

SED delete (inclusive) between 2 patterns only if 3rd pattern present

I'll keep this short. I want to use sed to delete sets of text in a file. Basically here is an example of what I have:

textbefore
PATTERN 1
  text1
    PATTERN 3
  text2
PATTERN 2
PATTERN 1
  text3
   text4
  text5
PATTERN 2
textafter

What I want to happen is to PATTERN 1 through PATTERN 2 (inclusive) but ONLY if it contains PATTERN3 between the two.

This is basically what I want to see:

textbefore
PATTERN 1
  text3
   text4
  text5
PATTERN 2
textafter

Any help would be appreciated

Upvotes: 4

Views: 412

Answers (5)

potong
potong

Reputation: 58578

This might work for you (GNU sed):

sed '/PATTERN 1/,/PATTERN 2/!b;/PATTERN 1/h;//!H;/PATTERN 2/!d;g;/PATTERN 3/d' file

Treat lines not between the first two patterns as normal. For lines between the first two patterns, save those lines in the hold space and delete them except if the line is the second of the two patterns. For the last line between the two patterns, check if the third pattern is present and if it is delete those lines otherwise print as normal.

Upvotes: 0

Ed Morton
Ed Morton

Reputation: 204648

With GNU awk for multi-char RS and RT:

$ gawk -vRS='PATTERN 2' '{ORS=(sub(/(^|\n)PATTERN 1.*PATTERN 3.*/,"")?"":RT)}1' file
textbefore
PATTERN 1
  text3
   text4
  text5
PATTERN 2
textafter

or with any awk:

$ awk '
    { buf = buf $0 RS }
    /PATTERN 2/ { sub(/PATTERN 1.*PATTERN 3.*/,"",buf); printf "%s",buf; buf="" }
    END { printf "%s",buf }
' file
textbefore
PATTERN 1
  text3
   text4
  text5
PATTERN 2
textafter

Upvotes: 0

anubhava
anubhava

Reputation: 786289

Using perl we have advantage of using lookarounds in regex:

perl -00 -pe 's/(?s)PATTERN 1(?=.*?PATTERN 3)((?!PATTERN [12]).)*PATTERN 2\R+//g' file
textbefore    
PATTERN 1
  text3
   text4
  text5
PATTERN 2
textafter

RegEx Explanation:

?s                   # to make dot match newlines also
PATTERN 1            # to match literal PATTERN 1
(?=.*?PATTERN 3)     # lookahead to make sure PATTERN 3 comes before the
                     # next PATTERN 2
((?!PATTERN [12]).)* # negative lookahead to make sure PATTERN 1 or PATTERN 2
                     # isn't matched before next PATTERN 2
PATTERN 2            # to match literal PATTERN 2
\R+                  # to match 1 or more new lines

Upvotes: 0

glenn jackman
glenn jackman

Reputation: 247210

Works with GNU sed, at least.

sed '/PATTERN 1/{:a;/PATTERN 2/bb;N;ba;:b;/PATTERN 3/d;}' file

with commentary:

#!/usr/bin/sed
/PATTERN 1/ {        # when start pattern is seen
  :a
  /PATTERN 2/ bb     # goto "b" if end pattern seen
  N                  # append next line to pattern space
  ba                 # and goto "a"
  :b
  /PATTERN 3/ d      # if this "block" contains patt, delete
}

Upvotes: 2

Jotne
Jotne

Reputation: 41460

If you like to try awk this should do:

awk '/PATTERN 1/ {s=1} s {a[++c]=$0} /PATTERN 3/ {p=1} /PATTERN 2/ {if (!p) for (i=1;i<=c;i++) print a[i];delete a;s=p=c=0;next} !s' file
textbefore
PATTERN 1
  text3
   text4
  text5
PATTERN 2
textafter

How it works:

awk '
/PATTERN 1/ {s=1}                   # If pattern "1" is found set flag "s"
s           {a[++c]=$0}             # If flag "s" is set store the row in array "a"
/PATTERN 3/ {p=1}                   # If pattern "3" is found set flag "p"
/PATTERN 2/ {                       # If pattern "2" is found
            if (!p)                 # Test if flag "p" is not set
                for (i=1;i<=c;i++)  # Loop "c" number of times
                    print a[i]      # Print data in array "a"
            delete a                # Delete array "a"
            s=p=c=0                 # Reset flag and counters
            next}                   # Skip to next line
!s                                  # If flag "s" is not set, print the line
' file                              # Read the file

Upvotes: 0

Related Questions