5

I need to grep a file from a line containing Pattern A to a first empty line. I used awk but I don't know how to code this empty line.

cat ${file} | awk '/Pattern A/,/Pattern B/'
herder
  • 412
  • 2
  • 5
  • 16
  • 1
    `awk` is perfectly capable of reading files; it doesn't need `cat` (so you get a UUOC — Useless Use of `cat` — award). Using `awk '/Pattern A/,/^$/ { print }' ${file}` would do the job. The blank lines seem useful to me to separate chunks of output; if you don't want them, change the action to `{ if (NF) print }` so it only prints lines with at least one field. – Jonathan Leffler May 23 '14 at 14:37

4 Answers4

15

sed might be best:

sed -n '/PATTERN/,/^$/p' file

To avoid printing the empty line:

sed -n '/PATTERN/,/^$/{/^$/d; p}' file

or even better - thanks jthill!:

sed -n '/PATTERN/,/^$/{/./p}' file

Above solutions will give more output than needed if PATTERN appears more than once. For that, it is best to quit after empty line is found, as jaypal's answer suggests:

sed -n '/PATTERN/,/^$/{/^$/q; p}' file

Explanation

  • ^$ matches empty lines, because ^ stands for beginning of line and $ for end of line. So that, ^$ means: lines not containing anything in between beginning and end of line.
  • /PATTERN/,/^$/{/^$/d; p}
    • /PATTERN/,/^$/ match lines from PATTERN to empty line.
    • {/^$/d; p} remove (d) the lines being on ^$ format, print (p) the rest.
  • {/./p} just prints those lines having at least one character.

With awk you can use:

awk '!NF{f=0} /PATTERN/ {f=1} f' file

Same as sed, if it has many lines with PATTERN it would fail. For this, let's exit once empty line is found:

awk 'f && !NF{exit} /PATTERN/ {f=1} f' file

Explanation

  • !NF{f=0} if there are no fields (that is, line is empty), unset the flag f.
  • /PATTERN/ {f=1} if PATTERN is found, set the flag f.
  • f if flag f is set, this is True, so it performs the default awk behaviour: print the line.

Test

$ cat a
aa
bb
hello
aaaaaa
bbb

ttt

$ awk '!NF{f=0} /hello/ {f=1} f' a
hello
aaaaaa
bbb
$ sed -n '/hello/,/^$/{/./p}' a
hello
aaaaaa
bbb
Community
  • 1
  • 1
fedorqui
  • 275,237
  • 103
  • 548
  • 598
8

Using sed:

sed -n '/PATTERN/,/^$/{/^$/q;p;}' file

Using regex range, you define your range from the PATTERN to blank line (/^$/). When you encounter a blank line, you quit else you keep printing.

Using awk:

awk '/PATTERN/{p=1}/^$/&&p{exit}p' file 

You enable a flag when you encounter your PATTERN. When you reach a blank line and flag is enabled, you exit. If not, you keep printing.

Another alternate suggested by devnull in the comments is to use pcregrep:

pcregrep -M 'PATTERN(.|\n)*?(?=\n\n)' file
Community
  • 1
  • 1
jaypal singh
  • 74,723
  • 23
  • 102
  • 147
  • 2
    I'm glad that you quit upon matching the blank line, else your `sed` expression would have been erroneous too. – devnull May 23 '14 at 15:54
  • 1
    Interesting that there are some who flag comments that point out errors in the answers! – devnull May 26 '14 at 16:44
  • @devnull Interesting indeed! `:P` – jaypal singh May 26 '14 at 16:48
  • I'm reminded of _pound fool_ -- the only difference being that here you have someone who won't downvote for losing 1 rep. – devnull May 26 '14 at 16:54
  • @devnull I flagged your comment in my answer as obsolete as it stated (more or less textually) "all sed versions of this answer are WRONG". I updated to the current version and commented so. As your comment was obsolete, I flagged it as so. I think it is a quite basic argument. – fedorqui May 26 '14 at 21:38
  • @fedorqui Yes. Updated the wrong answer by copy-pasting from another answer. And then remove traces by flagging the comment. This is how SO works. Now flag this one too. – devnull May 26 '14 at 23:41
  • @fedorqui [Creative commons](http://creativecommons.org/licenses/by-sa/3.0/), huh? Downvoted for copying. Flag it again. – devnull May 26 '14 at 23:42
  • @devnull I think you are making it bigger than it is. Good luck. And of course I will keep flagging obsolete comments. – fedorqui May 27 '14 at 08:10
  • @fedorqui Relax. I won't flag answers that you copy-paste from others. Good luck and keep copying. You can't prevent downvotes, though, for blatant copy/paste. – devnull May 27 '14 at 08:31
  • @devnull As you keep mentioning copying, it's fair to say that this answer was quite same as mine for a while, then edited to current nice version. Editing mine to indicate reference. Anyway, "exit after match" idea is quite logic when you notice what you said: if multiple PATTERN found, something breaks. Improving an answer by adding conditions for hypothetical situations is quite the idea of SO, don't think I should keep my not perfect answer as it was since I knew how to improve it. Both of us have been here time enough to know you are not fair saying that I copy. Relax, we need relax. – fedorqui May 27 '14 at 08:47
  • @fedorqui If an existing answer didn't do what you modified, it was perhaps ok. I consider it blatant copy/paste. YMMV. Moreover, I'm not sure of your claims given the [revision history](http://stackoverflow.com/posts/23831984/revisions). I wonder what would one do if these internet dollars were for real. – devnull May 27 '14 at 08:56
  • Relevant discussion: http://meta.stackoverflow.com/questions/256171/what-to-do-if-theres-a-better-answer-than-mine – devnull May 27 '14 at 17:11
3

I think this is a nice, readable Perl one-liner:

perl -wne '$f=1 if /Pattern A/; exit if /^\s*$/; print if $f' file
  • Set the flag $f when the pattern is matched
  • Exit if a blank line (only whitespace between start and end of line) is found
  • Print the line if the flag is set

Testing it out:

$ cat file
1
2
Pattern A
3
4
5
6

7
8
9

$ perl -wne '$f=1 if /Pattern A/; exit if /^$/; print if $f' file
Pattern A
3
4
5
6

Alternatively, based on the suggestion by @jaypal, you could do this:

perl -lne '/Pattern A/ .. 1 and !/^$/ ? print : exit' file

Rather than using a flag $f, the range operator .. takes care of this for you. It evaluates to true when "Pattern A" is found on the line and remains true indefinitely. When it is true, the other part will be evaluated and will print until a blank line is found.

Community
  • 1
  • 1
Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
  • or `perl -lne 'print if /Pattern A/../^$/ and !/^$/' file` – jaypal singh May 23 '14 at 15:17
  • @jaypal looks interesting - I've never seen the `/../` construction before, what's going on there? – Tom Fenech May 23 '14 at 15:19
  • They are [range operators](http://perldoc.perl.org/perlop.html#Range-Operators). Similar to what `awk` and `sed` have only not using `,`. – jaypal singh May 23 '14 at 15:21
  • @jaypal thanks for the link. I wasn't particularly sure what the `,` were doing either! – Tom Fenech May 23 '14 at 15:22
  • 1
    [sed](http://www.gnu.org/software/sed/manual/html_node/Addresses.html) and [awk](http://www.gnu.org/software/gawk/manual/html_node/Ranges.html#Ranges) define range using `,`. perl does the same by using `..` or `...`. – jaypal singh May 23 '14 at 15:24
  • @jaypal I modified your example slightly so it exits after the blank line. Thanks for the links and explanation. – Tom Fenech May 23 '14 at 18:04
2

Never use

/foo/,/bar/

in awk unless you want to get from the first occurrence of "foo" to the last occurrence of "bar" as it makes trivial jobs marginally briefer but even slightly more interesting requirements require a complete re-write.

Just use:

/foo/{f=1} f{print; if (/bar/) f=0}

or similar instead.

In the case the awk solution is:

awk '/pattern/{f=1} f{print; if (!NF) exit}' file
Ed Morton
  • 188,023
  • 17
  • 78
  • 185