33

How can I select first occurrence between two patterns including them. Preferably using sed or awk.

I have:

text
something P1 something
content1
content2
something P2 something
text
something P1 something
content3
content4
something P2 something
text

I want the first occurrence of the lines between P1 and P2 (including P1 line and P2 line):

something P1 something
content1
content2
something P2 something

10 Answers 10

34
sed '/P1/,/P2/!d;/P2/q'

...would do the job portably by deleting all lines which do !not fall within the range, then quitting the first time it encounters the end of the range. It does not fail for P2 preceding P1, and it does not require GNU specific syntax to write simply.

7
  • Excellent! Much better than mine.
    – muru
    Jan 23, 2015 at 22:04
  • 1
    @muru - It is often easier to avoid contortions if you try to target the autoprint - let the cycle work for you. That's the habit I've fallen into anyway. I think it's probably best described as a prune vs a select method - I tend to wind up negating a pattern rather than searching for it.
    – mikeserv
    Jan 23, 2015 at 22:26
  • This will hung when processing huge fileSize.
    – Brain90
    Dec 13, 2017 at 7:20
  • 1
    @Brain90 - shouldnt. if you can reliably reproduce your complaint you should address the maintainer of your sed... thats a bug in the sed youre running, and not in tbe script above.
    – mikeserv
    Aug 4, 2018 at 22:39
  • 1
    @mikeserv I would not have said it if I wasn't. Your concern over whether or not I care about a couple of characters is weird: I observed that the sed expression worked both with and without /P2/q on my system; that's it. I was curious about something and wanted to share what I found. Oct 10, 2019 at 21:28
13

In sed:

sed -n '/P1/,/P2/p; /P2/q'
  • -n suppresses the default printing, and you print lines between the matching address ranges using the p command.
  • Normally this would match both the sections, so you quit (q) when the first P2 matches.

This will fail if a P2 comes before P1. To handle that case, try:

sed -n '/P1/,/P2/{p; /P2/q}'
6
  • 2
    I disagree;  mikeserv’s answer is not any better than yours. Apr 16, 2019 at 18:24
  • @g-man - pshaw. but i was just thinking the same thing.
    – mikeserv
    Aug 25, 2019 at 7:33
  • 1
    @gman - nope. now i get it. mines way better. no{ stack}!
    – mikeserv
    Oct 23, 2019 at 6:59
  • 1
    You can avoid repeating the second pattern by using $, as in sed -n '/P1/,${p;/P2/q}'.
    – SU3
    Jun 10, 2020 at 4:44
  • 1
    @Erwann, my expression has the same behavior as the second one in muru's answer. If the first line that matches P1 also matches P2, the range consists only of that line. That may be the needed behavior or not depending on the use case.
    – SU3
    Mar 18, 2022 at 14:22
12

with awk

awk '/P1/{a=1};a;/P2/{exit}' file
something P1 something
content1
content2
something P2 something
2
awk '/P1/,/P2/{print;f=1} f&&/P2/{exit}' data

Quit immediately after print, not before.

1

If you want to skip the patterns themselves, here is the awk version:

awk '/P2/ {exit} /P1/ {f=1; next} f' file
1
  • Works for me. Could you add some more info about how the command works?
    – 0xAffe
    Apr 10, 2019 at 11:14
1

A simpler awk solution (sort-of halfway between iruvar’s answer and muru’s answer, but not using a variable):

awk '/P1/,/P2/ { print }  /P2/ { exit }'

and, as muru noted, if the first P2 appears before the first P1, this will print nothing.

Of course, if you want to print all the P1-P2 ranges:

something P1 something
content1
content2
something P2 something
something P1 something
content3
content4
something P2 something

just leave out the exit part:

awk '/P1/,/P2/ { print }'
0

To skip the patterns themselves, and show only first matching block in single GNU sed:

sed -nre '/STARTPATTERN/ {:a;n;/ENDPATTERN/{b;};p;ba}' file
1
  • That prints all matching blocks. For first matching block: sed -nE '/STARTPATTERN/ {:a;n;/ENDPATTERN/q;p;ba}' file Feb 28, 2022 at 2:31
0
awk '/something P1/{f=1}/something P2/{f=0;print;exit}f'  file

output

something P1 something
content1
content2
something P2 something
0

sed: (from @SU3's comment):

sed -n '/P1/,${p;/P2/q}'

awk:

awk '/P1/,0 {print; if ( /P2/ ) exit}' file

Both take the same approach: Within the range /P1/ to end of input, print and then exit if P2 is matched.

Both are DRY. P1 and P2 are specified only once.

Both pass the corner case of P2 preceding P1.

0

Using Raku (formerly known as Perl_6)

raku -ne 'state $i=0; .put if m/ P1 {last if $i++ == 1} /fff/ P2 /;'

#OR

raku -e 'my $i=0; .put if m/ P1 {last if $i++ == 1} /fff/ P2 / for lines;' 

#OR

raku -e 'my $i=0; for lines() { .put if m/ P1 {last if $i++ == 1} /fff/ P2 / };'  

Sample Input:

text
something P1 something
content1
content2
something P2 something
text
something P1 something
content3
content4
something P2 something
text

Sample Output (1):

something P1 something
content1
content2
something P2 something

The above answer is coded in Raku, a member of the Perl-family of programming languages. Explaining the first answer briefly, Raku's non-autoprinting -ne command line flags are employed to run code over each line of input. Raku's m/<start>/fff/<stop>/ sed-like "flip-flop" operator is employed which takes linewise input (special-cased within the Rakudo compiler), enabling the return of text blocks that start with the first matching line and stop with the second matching line. The variable $i is stated once at the beginning of the loop to equal 0 zero. Finally, Raku also allows code blocks to execute within Regexes upon matching.

Putting it all together, when the sed-like "flip-flop" operator encounters a full match in both (start,stop) halves of the fff regex operator, $i gets incremented to 1, and thus lasts out of the loop, so only the first complete match is put. [If you want to output all matches, simply omit the {last if $i++ == 1} code block].

Note, it's simple enough to omit the two 'sentinal' lines in the return by changing fff to ^fff^, giving the following result:

Sample Output (2):

content1
content2

Further testing: All code above handles the case where P2 comes before P1. Additionally, all code above handles the case where P1 and P2 occur on the same line, WITHOUT terminating prematurely. If such "single line text blocks" are desired/preferred in the output, use Raku's "non-sed-like" ff operator instead of the fff operator.

https://docs.raku.org/routine/fff
https://raku.org

You must log in to answer this question.

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