32

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
29
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 '15 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 '15 at 22:26
  • This will hung when processing huge fileSize.
    – Brain90
    Dec 13 '17 at 7:20
  • @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 '18 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 '19 at 21:28
11

with awk

awk '/P1/{a=1};a;/P2/{exit}' file
something P1 something
content1
content2
something P2 something
10

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}'
4
  • 2
    I disagree;  mikeserv’s answer is not any better than yours. Apr 16 '19 at 18:24
  • @g-man - pshaw. but i was just thinking the same thing.
    – mikeserv
    Aug 25 '19 at 7:33
  • 1
    @gman - nope. now i get it. mines way better. no{ stack}!
    – mikeserv
    Oct 23 '19 at 6:59
  • You can avoid repeating the second pattern by using $, as in sed -n '/P1/,${p;/P2/q}'.
    – SU3
    Jun 10 '20 at 4:44
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 '19 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
0
awk '/something P1/{f=1}/something P2/{f=0;print;exit}f'  file

output

something P1 something
content1
content2
something P2 something

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.