54

With grep I know how to set the context to a fixed number of lines. Is it possible to show a context based on an arbitrary string condition, like set after-context to "until the next blank line"?

Or possibly some other combination of tools?

Basically I have a log file of contiguous lines, with blank lines separating the "events" I want to search for a string in the log file, but show the whole event....

William Pursell
  • 204,365
  • 48
  • 270
  • 300
pixelearth
  • 13,674
  • 10
  • 62
  • 110
  • This is possible with other tools. If you post an example, you'll get specific answers. – Lev Levitsky Nov 23 '12 at 18:44
  • You probably want to use `sed` or `awk` for that... – dmckee --- ex-moderator kitten Nov 23 '12 at 18:44
  • How can i do inverse of this. I have a log file with call stack. i dont want to print a call stack but want all other call stacks – Nikole Apr 09 '14 at 21:54
  • I've been looking for this also, but for piping things into grep. – Leo Ufimtsev Jul 13 '15 at 13:41
  • @pixelearth curious - did you find any of these to suit your need well? I notice none have been accepted. – Randall Apr 21 '16 at 20:36
  • @Randall I haven't accepted anything because none of them fulfill the question about how to set the grep context. Since they use multiple other tools, the other answers can all be useful to others based on the number of upvotes. But none of them are more "right" to me than the others. – pixelearth Oct 18 '17 at 06:58

5 Answers5

55

It sounds like you need sed:

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

Don't print by default (-n). For lines that match /pattern/ up to an empty line /^$/, print.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 2
    Note that this does not show the entire event, but misses those lines of the event preceding pattern. – William Pursell Nov 23 '12 at 23:05
  • 5
    To clearify: The command is of the format `','`. It finds inclusive address ranges (line ranges) starting with a match of `pattern` and ending with a match of a following empty line (matching `^$`). For each range function is executed (`p`: print incoming lines). In order to exclude empty lines from output use `sed -n '/pattern/,/^$/{/^$/!p}' file`. Here `/^$/!p` means print any incoming line not matching `^$`. – valid Feb 14 '13 at 12:25
  • 3
    This worked perfectly for me, with one exception: my source input had DOS-style newlines (\r\n), so I had to do this instead: `sed -n '/pattern/,/^\r$/p' file` – bmaupin Feb 24 '15 at 18:02
  • This works nicely if you don't have special characters in your match, if you're doing a match for directories, you're screwed. – Dmytro Lysak Nov 08 '22 at 11:55
  • @DmytroLysak — why? `sed -ne '\%/home/user/bin/%p'` prints only the files under `/home/user/bin` in standard input. – Jonathan Leffler Nov 08 '22 at 13:59
30

A simple solution is:

awk '/pattern/' RS= input-file

Setting RS to the empty string makes awk treat blank lines as the record separator, and the simple rule /pattern/ causes awk to print any record that matches the pattern, which can be any extended regular expression.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • 2
    This will show the entire event, including preceding lines (back to a blank line) – Randall May 30 '18 at 15:06
  • @Randall (better late than never, I suppose), the OP states "I want to search for a string in the log file, but show the whole event....", so showing the entire record is the desired behavior. – William Pursell Feb 09 '22 at 19:14
  • I agree; the whole event is desired, and this is a _really_ nice simple solution for that. The OP's title of the question is about how to "set the _'grep after'_ context, so my comment was really aimed at the TL;DR crowd who only read the title – Randall Feb 17 '22 at 15:45
9

Here's a (tested) awk solution, separated onto multiple lines for readability:

awk '/pattern/ {do_print=1}
     do_print==1 {print}  
     NF==0 {do_print=0}' input_file

This script will also print the blank line so it's easier to visually separate the different matched blocks. If you don't want the blank line, swap the 2 lines do_print==1 {print} and NF==0 {do_print=0}

Explanation:

  • awk: invoke the awk tool - it evaluates the input one line at a time sequentially.
  • '...'.: everything enclosed by the single quotes is given to awk as instructions. In this case we perform the quoted instructions on each line.
  • /pattern/ {do_print=1}: whenever a line contains "pattern", turn on the do_print flag
  • do_print==1 {print}: if the do_print flag is set to on, print the current line.
  • NF==0 {do_print=0}: NF stands for Number of Fields. awk delimits each line by spaces and tabs by default to break a line into fields. In this case trivially a blank line has no fields - so we turn off the do_print flag to stop printing when we see a line with NF == 0
sampson-chen
  • 45,805
  • 12
  • 84
  • 81
1

Personally I like the answer from @William Pursell as the before context is often useful (eg when grepping for things in ini files). If you actually want only the after context with awk you can do this:

$ cat demo.ini 
[foo]
aaa = 1
bbb = 2
ccc = 3

[bar]
eee = 8
fff = 0
ggg = 1

[baz]
xxx = 1
yyy = 0
zzz = 2
$ awk '/bar/,/^$/' demo.ini 
[bar]
eee = 8
fff = 0
ggg = 1

$ awk '/fff/,/^$/' demo.ini 
fff = 0
ggg = 1

$

Compare with the RS= version:

$ awk '/bar/' RS= demo.ini 
[bar]
eee = 8
fff = 0
ggg = 1
$ awk '/fff/' RS= demo.ini 
[bar]
eee = 8
fff = 0
ggg = 1
htaccess
  • 2,800
  • 26
  • 31
0

I extended the solution above to include empty line between the sections:

awk '/pattern/' RS= ORS="\n\n" input-file