44

I would like to search for a pattern in a file and prints 5 lines after finding that pattern.

I need to use awk in order to do this.

Example:

File Contents:

.
.
.
.
####PATTERN#######
#Line1
#Line2
#Line3
#Line4
#Line5
.
.
.

How do I parse through a file and print only the above mentioned lines? Do I use the NR of the line which contains "PATTERN" and keep incrementing upto 5 and print each line in the process. Kindly do let me know if there is any other efficient wat to do it in Awk.

GAD3R
  • 4,317
  • 1
  • 23
  • 34
tomkaith13
  • 1,717
  • 4
  • 27
  • 39
  • 3
    http://stackoverflow.com/questions/17908555/printing-with-sed-or-awk-a-line-following-a-matching-pattern – ntrp Aug 29 '14 at 21:46

7 Answers7

67

Another way to do it in AWK:

awk '/PATTERN/ {for(i=1; i<=5; i++) {getline; print}}' inputfile

in sed:

sed -n '/PATTERN/{n;p;n;p;n;p;n;p;n;p}' inputfile

in GNU sed:

sed -n '/PATTERN/,+7p' inputfile

or

sed -n '1{x;s/.*/####/;x};/PATTERN/{:a;n;p;x;s/.//;ta;q}' inputfile

The # characters represent a counter. Use one fewer than the number of lines you want to output.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • Nice use of the hold space as a countdown. – ninjalj Mar 15 '11 at 21:16
  • What if I want to match the pattern only in the last column ? For example, print the next line iff last column ($NF) is E. I want to ignore E in other columns. – discipulus Dec 22 '13 at 23:38
  • 1
    @lovedynasty: `awk '$NF ~ /^E$/ {for(i=1; i<=5; i++) {getline; print}}' inputfile` should work. – Dennis Williamson Dec 23 '13 at 15:23
  • 2
    If you are going to use sed (Gnu sed) then sed -n `/PATTERN/,+5p' file` might fit the bill. Or `sed -n /PATTERN/{N;N;N;N;N;p}' file`. – potong Aug 02 '14 at 10:50
  • This `awk` answer is much more flexible, making it more useful than the more compact versions which are nice on their own and to the point of the question. But I could do much more starting from this particular syntax: find pattern, print next two lines and combine them into one line, with specific print conditions (fields) for each one. – one-liner Jul 14 '17 at 11:44
  • 1
    @Kjuly: Note that potong's uses capital N's which appends to the pattern space (lower n replaces the pattern space). – Dennis Williamson Jun 11 '18 at 14:05
  • @DennisWilliamson ah right, let me delete original comment and append it here again: Just as a note here for sed's case, if you're under Mac OS (which I heard uses BSD sed), need extra `;` after `p`, i.e., `sed -n '/PATTERN/{n;p;n;p;n;p;n;p;n;p;}'` inputfile, or like @potong said, `sed -n '/regex/{N;N;N;N;N;p;}'` file. – Kjuly Jun 12 '18 at 05:54
15
awk '
{ 
    if (lines > 0) {
        print;
        --lines;
    }
}

/PATTERN/ {
    lines = 5
}

' < input

This yields:

#Line1
#Line2
#Line3
#Line4
#Line5
johnsyweb
  • 136,902
  • 23
  • 188
  • 247
  • 2
    There is an issue with this code. If the PATTERN repeats after less than 5 lines, the loop restarts rather than finish printing all 5 lines. Answer by Dennis works better. – Ruchi Mar 18 '11 at 03:39
  • @Ruchi You're right. Dennis' is a better answer (and I upvoted it). I think it came in after @tomkaith13 accepted my own. I can only assume that my answer solves his problem, even if it is not the most resilient. – johnsyweb Mar 18 '11 at 10:47
  • 1
    @Ruchi: But Dennis' answer doesn't print all 5 lines following the second occurence in this case. I think it depends on what you want. You might want to print the first lines after the second occurence (those that are within the 5 lines range of the previous one) twice: once for the first (including the second occurence) and twice for the second occurence. This really depends on the use case and wasn't specified by the OP. – mschilli Sep 03 '13 at 07:08
  • What if I want to match the pattern only in the last column ? For example, print the next line iff last column ($NF) is E. I want to ignore E in other columns – discipulus Dec 22 '13 at 23:42
  • 1
    @lovedynasty: This is a different question and should be asked as a fresh post. Have you tried `$NF ~ /PATTERN/ {` ? – johnsyweb Dec 22 '13 at 23:47
  • I made it work by using && $NF~/PATTERN/ works. Thanks – discipulus Dec 22 '13 at 23:53
  • @lovedynasty: You shouldn't need `/PATTERN/ && `! – johnsyweb Dec 22 '13 at 23:56
13

grep "PATTERN" search-file -A 5 will do the job for you if decide to give grep a chance.

Edit: You can call grep using system() function from inside your awk script as well.

mschilli
  • 1,884
  • 1
  • 26
  • 56
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • I thought that, too. But that yields the line containing 'PATTERN', which doesn't seem to match the requirements. – johnsyweb Mar 15 '11 at 19:19
  • 4
    @Johnsyweb: `grep -A6 | tail -5` ? – ninjalj Mar 15 '11 at 19:27
  • @ninjalj: What happens when 'PATTERN' appears inside the input file more than once? – johnsyweb Mar 15 '11 at 19:33
  • @anubhava: forking to `grep` within `awk`? Yuck! – johnsyweb Mar 15 '11 at 19:34
  • @Johnsyweb: No I won't recommend calling grep from awk either but since OP specifically said that he needs to use awk only that's why just added that edit line. – anubhava Mar 15 '11 at 19:46
  • 1
    I think grep -A is a gnu extension, you won't find that option on AIX or Solaris standard installs, but I don't have access to those OS's to confirm this right now. Cordially – shellter Mar 15 '11 at 20:05
  • 1
    You can install gnu utils on other unixes as well. Very neat info thanks. Using grep on a regular basis but didn't know it had -A and -B – akostadinov Aug 05 '13 at 13:03
  • @ninjalj: Shouldn't it be `grep -A5 | tail -5`? GNU `grep` 2.14 over here. – mschilli Sep 03 '13 at 07:24
  • 1
    @Johnsyweb: Besides the initial line that matched `PATTERN`, `grep -A5` will result in the same output as your code. However, things get more complicated with multiple occurences. – mschilli Sep 03 '13 at 07:30
11

awk to the rescue!

to print with the pattern line (total 6 lines)

$ awk '/^####PATTERN/{c=6} c&&c--' file

####PATTERN#######
#Line1
#Line2
#Line3
#Line4
#Line5

to skip pattern and print the next five lines

$ awk 'c&&c--; /^####PATTERN/{c=5}' file

#Line1
#Line2
#Line3
#Line4
#Line5
karakfa
  • 66,216
  • 7
  • 41
  • 56
  • Excellent improvement over Dennis' answer! – champost May 16 '16 at 18:58
  • @karakfa, never seen statements before patterns in awk `awk 'c&&c--; /^####PATTERN/{c=5}'`. Can we write any type of statement before pattern like print or assignment etc ? – Vicky May 02 '19 at 01:06
  • Not a statement but any expression that has a value (which will be interpreted as truth value). Corresponding statement to the expression will be the default statement, which is `{print}`. – karakfa May 02 '19 at 02:57
8

Edit: didn't notice PATTERN shouldn't be part of the output.

cat /etc/passwd | awk '{if(a-->0){print;next}} /qmaild/{a=5}'

or

cat /etc/passwd | awk ' found && NR-6 < a{print} /qmaild/{a=NR;found=1}'

The shortest I can come up with is:

cat /etc/passwd | awk 'a-->0;/qmaild/{a=5}'

Read as a tends to 0. /qmaild/ sets a to 5 :-)

ninjalj
  • 42,493
  • 9
  • 106
  • 148
  • 4
    Nice usage of the [`-->`](http://stackoverflow.com/q/1642028/78845) operator! But why are you using [`cat`](http://uuoc.com/)? – johnsyweb Mar 15 '11 at 19:14
  • To have the input clearly separate from the command the OP is interested in. – ninjalj Mar 15 '11 at 19:31
  • 3
    For the benefit of less experienced readers, surely the --> is the -- operator appended to the 'a' variable, with > representing 'greater than', yes? Awk can parse such concatenated syntax. Nice! – shellter Mar 15 '11 at 20:02
0

As limited as Johnsyweb's solution but using a different approach and GNU awk's advanced feature allowing full RegEx record separators resulting in a piece of code that, IMHO, is very awk-ish:

awk -F\\\n '{NF=5}NR-1' RS="PATTERN[^\n]*." OFS=\\\n

So if you want a stable solution that is also readable, go along with Dennis Williamson's answer (+1 for that one) but maybe you too simply enjoy the beauty of awk when looking at lines like this.

mschilli
  • 1,884
  • 1
  • 26
  • 56
0

Quick&dirty solution: awk '/PATTERN/ {i=0} { if (i<=5) {print $0}; i+=1} BEGIN {i=6}'

gelraen
  • 238
  • 1
  • 8