5

I would like to match a set of data between two patterns and remove this data and the start/end patterns but only for the first occurrence of the pattern.

So if this is the test data:

PATTERNSTART
LINE1
LINE2
LINE3
PATTERNEND
PATTERNSTART
LINE1
LINE2
LINE3
PATTERNEND
TESTLINE1
TESTLINE2
TESTLINE3
PATTERNSTART
LINE1
LINE2
LINE3
PATTERNEND

This will quite happy remove all the pattern matches and the lines in between but I only want to remove the first pattern match and the lines in between:

sed '/PATTERNSTART/,/PATTERNEND/d' testsed.txt

Output:

TESTLINE1
TESTLINE2
TESTLINE3

Required output:

PATTERNSTART
LINE1
LINE2
LINE3
PATTERNEND
TESTLINE1
TESTLINE2
TESTLINE3
PATTERNSTART
LINE1
LINE2
LINE3
PATTERNEND

Any sed ideas?

SnazzyBootMan
  • 669
  • 2
  • 15
  • 30

6 Answers6

4

It's a bit incredible-machiney, but this works:

sed '/PATTERNSTART/,/PATTERNEND/ { // { x; s/$/./; x; }; x; /.../! { x; d; }; x; }' filename

as follows:

/PATTERNSTART/,/PATTERNEND/ {   # in the pattern range
  // {                          # in the first and last line:
    x
    s/$/./                      # increment a counter in the hold buffer by
                                # appending a character to it. The counter is
                                # the number of characters in the hold buffer.
    x
  }
  x                             # for all lines in the range: inspect the
                                # counter
  /.../! {                      # if it is not three or more (the counter
                                # becomes three with the start line of the
                                # second matching range)
    x
    d                           # delete the line
  }
  x
}

The xs in that code are largely to ensure that the counter ends up back in the hold buffer when the whole thing is over. The // bit works because // repeats the last attempted regex, which is the start pattern of the range for its first line and the end pattern for the others.

Wintermute
  • 42,983
  • 5
  • 77
  • 80
  • It does work @Wintermute, if the lines between the patterns change in number it still works. What does /.../! do? If what is not three or more? – SnazzyBootMan Mar 05 '15 at 23:21
  • 1
    `x` exchanges pattern space and hold buffer. The `// { x; s/$/./; x; }` bit keeps a counter in the hold buffer by appending another character to the hold buffer in every boundary line of a matching range -- the counter is the number of characters in the hold buffer. `x; /.../! { x; d; }` inspects the pattern space and executes `x; d;` (i.e., swaps back and deletes the line) if its contents do not (that's what the `!` is for) match the regex `...`, which is the case when it contains three or more characters -- i.e., when the counter is three or higher. – Wintermute Mar 05 '15 at 23:25
3

Just use awk (the cat -n is just so you can see which line numbers are being printed):

$ cat -n file | awk '/PATTERNSTART/{f=1;++c} !(f && c==1); /PATTERNEND/{f=0}'
     6  PATTERNSTART
     7  LINE1
     8  LINE2
     9  LINE3
    10  PATTERNEND
    11  TESTLINE1
    12  TESTLINE2
    13  TESTLINE3
    14  PATTERNSTART
    15  LINE1
    16  LINE2
    17  LINE3
    18  PATTERNEND

Set the test on c to be the occurrence number of whatever block you want to skip:

$ cat -n file | awk '/PATTERNSTART/{f=1;++c} !(f && c==2); /PATTERNEND/{f=0}'
     1  PATTERNSTART
     2  LINE1
     3  LINE2
     4  LINE3
     5  PATTERNEND
    11  TESTLINE1
    12  TESTLINE2
    13  TESTLINE3
    14  PATTERNSTART
    15  LINE1
    16  LINE2
    17  LINE3
    18  PATTERNEND
Ed Morton
  • 188,023
  • 17
  • 78
  • 185
2
sed '/PATTERNSTART/,/PATTERNEND/{0,/PATTERNEND/d}' file
mop
  • 423
  • 2
  • 11
0

You can do this with that (quite ugly I admit) sed code:

sed -e '/PATTERNSTART/,/PATTERNEND/{ /PATTERNEND/b after; d; :after; N; s/^.*\n//; :loop; n; b loop; }' testsed.txt

Let's look at it more closely:

sed -e '/PATTERNSTART/,/PATTERNEND/{

 /PATTERNEND/b after; # if we're at the end of match, go to the hack
 d;                   # if not, delete the line and start a new cycle

 :after;              # Begin "end of part to delete"
 N;                   # get the next line...
 s/^.*\n//;           # ...and forget about this one

                      # We now only have to print everything:
 :loop; n; b loop;

                      # And you sir, have your code!
}' testsed.txt
PatJ
  • 5,996
  • 1
  • 31
  • 37
0

This might work for you (GNU sed):

sed '/PATTERNSTART/,/PATTERNEND/{x;/./{x;b};x;/PATTERNEND/h;d}' file

This uses the hold space as a switch. Check the file for the desired range of lines. If encountered and the hold space is not empty, the first range has already been deleted so bail out and print as normal. If not, set the switch on the last pattern match and delete all lines within the range.

potong
  • 55,640
  • 6
  • 51
  • 83
-1

Use

sed -e '/PATTERNSTART/,/PATTERNEND/d' -e '/PATTERNEND/q' some_file.txt

The q command causes sed to quit.

randomusername
  • 7,927
  • 23
  • 50