9

I want to search for a particular string in a certain file. If I found the string, I also want to print the line X lines before (or after) that line.

Can this with grep or awk, or do I need a combination?

I would like to have something like this, but not with all trailing/leading lines before or after a hit, only the Xth.

For example, if my input looks like:

line1 with a pattern
line2
line3
line4 with a pattern
line5
line6
line7 with a pattern
...

I e.g. want to search for the word 'pattern' and output that line + the line that is 2 lines after that, but not the line that directly follows the line with the pattern. So the desired output is:

line1 with a pattern
line3
line4 with a pattern
line6
line7 with a pattern
...
MaVe
  • 213
  • 1
  • 2
  • 5

4 Answers4

16

grep will do this for you, with options -A (after) and -B (before), and -C (context). An example I often use is:

   sudo lspci -vnn | grep -i net -A 12

because this command will show 12 lines after the match, which includes the driver used to control the network (-i net) card. In general, the command is:

  grep text_to_search -A n -B m file.extension

which will output m lines before the match, and n lines after the match. Or you can use

 grep text_to_search -C n file.extension

to show a total of n lines surrounding the text found (half before, half after the match).

MariusMatutiae
  • 48,517
  • 12
  • 86
  • 136
3

After looking into this question a little bit I think I have to revise my comment about the feasability using standard GNU tools without scripting. This may be very hard to do because of the special cases.

If you don't mind using awk I could offer the following solution. This script context.awk ist still rather concise:

{
    lines[NR] = $0
    if (dump[NR]) {
        print $0;
    if ($0 ~ Pattern) {
        if (NR-Delta in lines) {
            print "---"
            print lines[NR-Delta]
        }
        dump[NR+Delta] = 1
        print $0;
    }
    if (NR-Delta in lines) 
        delete lines [NR-Delta];
}

It has to be called as follows:

awk -v Delta=X -v Pattern=PATTERN -f context.awk sample.txt

where X is the desired "context distance" and PATTERN the search pattern. The script tries to seperate multiple pattern contexts in the file by printing lines with --- in between. So, for example, the following sample.txt

line1
line2
line3 XXX
line4
line5
line6
line7 XXX
line8
line9
line10 XXX
line11

using this call

awk -v Delta=3 -v Pattern=XXX -f context.awk sample.txt

will yield the followng output

line3 XXX
line6
---
line4
line7 XXX
line10 XXX
---
line7 XXX
line10 XXX
2

The following sed command seems to work for the case you describe which I would restate as: print the line matching my pattern, do not print the immediately following line, also print the line two lines after my pattern, then keep searching for pattern matches.

sed -n -e '/with a pattern/ {h;n;n;H;x;p}' file

It seems a bit ugly, but by adding additional n (skip line, move to the next one) and H (keep this line by appending it to the hold buffer) commands you can construct an arbitrary keep/skip relationship following a pattern match.

Note that following the match we initially copy the matching line to the hold buffer with the h command. Finally we swap the hold and pattern space with x and print the pattern space with p. At this point sed will continue processing the file line-by-line looking for another match.

A handy reference for sed commands can be found here.

deaks
  • 121
0

grep -A2 <pattern> file | grep -B1 <pattern> | grep -v "\-\-" works for me:

user@box /tmp $ grep -A2 "with a pattern" test.txt | grep -B1 "with a pattern" | grep -v "\-\-"
line1 with a pattern
line3
line4 with a pattern
line6
line7 with a pattern

user@box /tmp $ cat test.txt 
line1 with a pattern
line2
line3
line4 with a pattern
line5
line6
line7 with a pattern
CHK
  • 607