4

Test file is given below:

PATTERN1
a
b
c
PATTERN2
d
e
f
PATTERN2
g
h

I want to print line between PATTERN1 and 2nd match of PATTERN2:

PATTERN1
a
b
c
PATTERN2
d
e
f
PATTERN2
6

Here's one way to do it with sed:

sed '/PATTERN1/,$!d;/PATTERN2/{x;//{x;q;};g;}' infile

This just deletes all lines (if any) up to the first occurrence of PATTERN1 and then, on each line that matches PATTERN2 it exchanges buffers. If the new pattern space also matches, it means it's the 2nd occurrence so it exchanges back and quits (after auto-printing). If it doesn't match it means it's the 1st occurrence so it copies the hold space content over the pattern space via g (so now there's a line matching PATTERN2 in the hold buffer) and goes on...
and another way with awk:

awk '/PATTERN1/{t=1}; t==1{print; if (/PATTERN2/){c++}}; c==2{exit}' infile

It starts printing and counting lines matching PATTERN2 only when encountering a line matching PATTERN1 and exits when counter reaches 2.

  • in my case PATTERN1 is a variable. means xx=PATTERN1 so i want to use $xx instead of PATTERN1. but the awk command is not working with it. can you help me. i tried awk '/$xx/{t=1}; t==1{print; if (/PATTERN2/){c++}}; c==2{exit}' infile but it is not working. – sachin Jun 29 '16 at 3:52
  • @sachin - awk -v m="${xx}" '$0~m{t=1}; t==1{print; if (/PATTERN2/){c++}}; c==2{exit}' infile – don_crissti Jun 29 '16 at 8:58
  • in another case, it should print data only if the PATTERN1 start the line. any solution ? – sachin Jul 8 '16 at 4:06
  • please provide solution to find the pattern at the start of line in the given command. awk -v m="${xx}" '$0~m{t=1}; t==1{print; if (/PATTERN2/){c++}}; c==2{exit}' infile . because in my case the pattern 1 is a variable and coming in the start of line. – sachin Jul 8 '16 at 11:37
  • thanks for the reply, its working. can you please explain the whole command ? – sachin Jul 8 '16 at 11:48
5

The right tool for this job is pcregrep:

pcregrep -M 'PATTERN1(.|\n)*PATTERN2' file

where option -M allows pattern to match more than one line and (.|\n)* match any character or newline zero or more times.

Notice that we took advantage of the greediness of the grep. If you would want to print form pattern1 up to the first occurrence of the pattern2, then non-greedy *? should be used instead of *.

As a generalization, to print up to the nth occurrence of PATTERN2:

pcregrep -M 'PATTERN1((.|\n)*?PATTERN2){n}' file

Change n to actual number you need.

  • As you are using multiline flag, so why not just pcregrep -M 'PATTERN1.*PATTERN2' file? – heemayl Jun 29 '16 at 1:51
  • n my case PATTERN1 is a variable. means xx=PATTERN1 so i want to use $xx instead of PATTERN1. but the command provided by you , is not working with it. can you help me. i tried pcregrep -M '$xx((.|\n)*?PATTERN2){2}' file infile but it is not working – sachin Jun 29 '16 at 3:57
  • @heemayl Because you have to provide both: -M and \n, otherwise it doesn't work. – jimmij Jun 29 '16 at 7:49
  • @sachin Try pcregrep -M "$xx"'((.|\n)*?PATTERN2){2}' file. The variable have to be inside double quotes, otherwise it is not expanded. – jimmij Jun 29 '16 at 7:51
5

Just use flags:

$ awk '/PATTERN1/{flag=2;next} flag; /PATTERN2/{flag--}' file
a
b
c
PATTERN2
d
e
f
PATTERN2

That is: when you find PATTERN1 set the flag to a positive value; in particular, 2. Then, when you find PATTERN2, decrease that flag in one. This way, it will exhaust after the second match. In between, use flag as a value that triggers the {print $0} when it has a true value (2 or 1).

  • This will fail in any of the following circumstances: 1) if there's at least one line matching PATTERN2 before the line matching PATTERN1 (but not immediately before it) 2) if there are more than two lines matching PATTERN2 after the line matching PATTERN1 and the third line matching PATTERN2 isn't the last line in the file or 3) if there's a second line matching PATTERN1 in between the first two (of several) lines matching PATTERN2 that follow the first line matching PATTERN1 – don_crissti Jun 28 '16 at 20:29
  • Would it be possible to not print the last PATTERN2? – Markus Lindberg Jun 18 '18 at 15:14
  • @MarkusLindberg you should either create some kind of buffering (otherwise, how do you know if it is the last PATTERN2?) or read the file backwards – fedorqui Jul 4 '18 at 6:39
2

If we told regarding sed is to much easy to collect nesessary lines then print

sed -n '
    /PATTERN1/{
        :1
        $!N
        /\(PATTERN2\).*\1/!b1
        p
    }
' file

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.