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

4 Answers 4

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.

8
  • 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, 2016 at 3:52
  • @sachin - awk -v m="${xx}" '$0~m{t=1}; t==1{print; if (/PATTERN2/){c++}}; c==2{exit}' infile Jun 29, 2016 at 8:58
  • in another case, it should print data only if the PATTERN1 start the line. any solution ?
    – sachin
    Jul 8, 2016 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, 2016 at 11:37
  • thanks for the reply, its working. can you please explain the whole command ?
    – sachin
    Jul 8, 2016 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.

4
  • As you are using multiline flag, so why not just pcregrep -M 'PATTERN1.*PATTERN2' file?
    – heemayl
    Jun 29, 2016 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, 2016 at 3:57
  • @heemayl Because you have to provide both: -M and \n, otherwise it doesn't work.
    – jimmij
    Jun 29, 2016 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, 2016 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).

3
  • 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 Jun 28, 2016 at 20:29
  • Would it be possible to not print the last PATTERN2? Jun 18, 2018 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, 2018 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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .