27

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
23
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.

|improve this answer|||||
  • 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. – Alexej Magura Oct 10 '19 at 21:28
9

with awk

awk '/P1/{a=1};a;/P2/{exit}' file
something P1 something
content1
content2
something P2 something
|improve this answer|||||
8

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}'
|improve this answer|||||
  • 1
    I disagree;  mikeserv’s answer is not any better than yours. – G-Man Says 'Reinstate Monica' 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
1

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

awk '/P2/ {exit} /P1/ {f=1; next} f' file
|improve this answer|||||
  • 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 }'
|improve this answer|||||
1
awk '/P1/,/P2/{print;f=1} f&&/P2/{exit}' data

Quit immediately after print, not before.

|improve this answer|||||
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
|improve this answer|||||

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.