5

Usually grep command is used to display the line contaning the specified pattern. Is there any way to display n lines before and after the line which contains the specified pattern?

Can this will be achieved using awk?

Sachin Chourasiya
  • 18,417
  • 31
  • 80
  • 95

6 Answers6

7

Yes, use

grep -B num1 -A num2 

to include num1 lines of context before the match, and num2 lines of context after the match.

EDIT:

Seems the OP is using AIX. This has a different set of options which doesn't include -B and -A

this link describes grep on AIX 4.3 (it doesn't look promising)

Matt's perl script might be a better solution.

pavium
  • 13,768
  • 4
  • 29
  • 45
4

Here is what I usually do on AIX:

before=2  << The number of lines to be shown Before >>
after=2   << The number of lines to be shown After >>
grep -n <pattern> <filename> | cut -d':' -f1 |  xargs  -n1 -I % awk "NR<=%+$after && NR>=%-$before" <filename>

If you do not want the extra 2 varialbles you can always use it an a one line:

grep -n <pattern> <filename> | cut -d':' -f1 |  xargs  -n1 -I % awk 'NR<=%+<<after>> && NR>=%-<<before>>' <filename>

Suppose I have a pattern 'stack' and the filename is flow.txt I want 2 lines before and 3 lines after. The the command will be like:

grep -n 'stack' flow.txt | cut -d':' -f1 |  xargs  -n1 -I % awk 'NR<=%+3 && NR>=%-2' flow.txt

I want 2 lines before and only - the the command will be like:

grep -n 'stack' flow.txt | cut -d':' -f1 |  xargs  -n1 -I % awk 'NR<=% && NR>=%-2' flow.txt

I want 3 lines after and only - the the command will be like:

grep -n 'stack' flow.txt | cut -d':' -f1 |  xargs  -n1 -I % awk 'NR<=%+3 && NR>=%' flow.txt

Multiple Files - change it for Awk & grep. From above for the pattern 'stack' with the filename is flow.* - 2 lines before and 3 lines after. The the command will be like:

    awk 'BEGIN { 
    before=1; after=3; pattern="stack";
    i=0; hold[before]=""; afterprints=0}
    { 
    #Print the lines from the previous Match
    if (afterprints > 0)
      {
      print FILENAME ":" FNR ":" $0
      afterprints-- #keep a track of the lines to print after - this can be reset if a match is found
      if (afterprints == 0) print "---"
      }
    #Look for the pattern in current line
    if ( match($0, pattern) > 0 )
      {
      # print the lines in the hold round robin buffer from the current line to line-1
      #  if (before >0)  => user wants lines before avoid divide by 0 in %
      #  and afterprints => 0 - we have not printed the line already
      for(j=i; j < i+before && before > 0 && afterprints == 0 ; j++)
        print hold[j%before]
      if (afterprints == 0) #  print the line if we have not printed the line already
        print FILENAME ":" FNR ":" $0
      afterprints=after
      }
    if (before > 0) # Store the lines in the round robin hold buffer
      { hold[i]=FILENAME ":" FNR ":" $0
        i=(i+1)%before }
  }' flow.*
user 923227
  • 1,963
  • 3
  • 20
  • 39
  • how should I rewrite it when I want to match more file like flow.* – hudi Aug 25 '15 at 08:16
  • you can replace flow.txt with flow.* in 2 places once for the grep and once for awk. for example - for the pattern 'stack' and the filename is flow.* - 2 lines before and 3 lines after. The the command will be like: `grep -n 'stack' flow.* | cut -d':' -f1 | xargs -n1 -I % awk 'NR<=%+3 && NR>=%-2' flow.*` – user 923227 Aug 25 '15 at 17:19
  • Updated the post above for clarity. – user 923227 Aug 25 '15 at 17:25
  • I dont think it will be working because cut now return name of file and no line of occurency – hudi Aug 26 '15 at 05:49
  • Yes you are right. We can use -f2 `cut -d':' -f2` Let me know if it works. – user 923227 Aug 26 '15 at 07:00
  • yes but now it takes number of one line and it print this line with all files becauce in awk you did not define file from cut – hudi Aug 26 '15 at 07:52
  • Hi Hudi, Thanks. I do not have access to an AIX anymore to explore the possibilities - hopefully the Awk script updated can work in place of grep. – user 923227 Aug 26 '15 at 21:58
1

From the tags, it's likely that the system has a grep that may not support providing context (Solaris is one system that doesn't and I can't remember about AIX). If that is the case, there's a perl script that may help at http://www.sun.com/bigadmin/jsp/descFile.jsp?url=descAll/cgrep__context_grep.

Matt
  • 499
  • 2
  • 5
1

If you have sed you could use this shell script

BEFORE=2
AFTER=3
FILE=file.txt
PATTERN=pattern
for i in $(grep -n $PATTERN $FILE | sed -e 's/\:.*//')
  do head -n $(($AFTER+$i)) $FILE | tail -n $(($AFTER+$BEFORE+1))
done

What it does is, grep -n prefixes each match with the line it was found at, the sed strips all but the line it was found at. Then you use head to get the lines up to the line it was found on plus an additional $AFTER lines. That's then piped to tail to just get $BEFORE + $AFTER + 1 lines (that is, your matching line plus the number of lines before and after)

Puppe
  • 4,703
  • 23
  • 27
  • This is the version which is the closest to the output of GNU grep -A -B -C (except this doesn't output a group separator (--)). – Bruce Nov 24 '17 at 11:21
0

Sure there is (from the grep man page):

   -B NUM, --before-context=NUM
          Print  NUM  lines  of  leading  context  before  matching lines.
          Places  a  line  containing  a  group  separator  (--)   between
          contiguous  groups  of  matches.  With the -o or --only-matching
          option, this has no effect and a warning is given.

   -A NUM, --after-context=NUM
          Print NUM  lines  of  trailing  context  after  matching  lines.
          Places   a  line  containing  a  group  separator  (--)  between
          contiguous groups of matches.  With the  -o  or  --only-matching
          option, this has no effect and a warning is given.

and if you want the same amount of lines before AND after the match, use:

   -C NUM, -NUM, --context=NUM
          Print NUM lines of output context.  Places a line  containing  a
          group separator (--) between contiguous groups of matches.  With
          the -o or --only-matching option,  this  has  no  effect  and  a
          warning is given.
Puppe
  • 4,703
  • 23
  • 27
0

you can use awk

awk 'BEGIN{t=4}
c--&&c>=0
/pattern/{ c=t; for(i=NR;i<NR+t;i++)print a[i%t] }
{ a[NR%t]=$0}
' file

output

$ more file
1
2
3
4
5
pattern
6
7
8
9
10
11

$ ./shell.sh
2
3
4
5
6
7
8
9
ghostdog74
  • 286,686
  • 52
  • 238
  • 332