Paul Ericson
Paul Ericson

Reputation: 917

Display n lines before and m lines after a pattern match were n & m are themselves pattern matches

I have data like this:

foo
...
bar
...
pattern
...
]

I need to first match to 'pattern' and then display everything before 'pattern' up to 'foo' and everything after pattern down to ']'

grep should do this:

grep pattern -A grep foo -B grep ]

But alas it does not.

Answer doesn't need to include grep. awk, sed and others welcome.

Upvotes: 1

Views: 763

Answers (3)

qqibrow
qqibrow

Reputation: 3022

single line using perl:

perl -wln -0777 -e 'm/foo((?!foo).)*pattern[^\]]*\]/s and print $&;' [filename]

input:

foo
foo
...
bar
...
pern
...
]
]
foo
... 
pattern
]
]
foo
]

output:

perl -wln -0777 -e 'm/foo((?!foo).)*pattern[^\]]*\]/s and print $&;' testtest
foo
... 
pattern
]

analysis on regex101

Some key points:

  1. turn on single line mode with m/.../s in perl refer to this post:
  2. regex foo((?!foo).)*pattern[^\]]*\]
    • foo match the first foo
    • ((?!foo).)* avoid matching foo in matching part using negative lookahead
    • pattern match pattern
    • [^\]]*\] the following part should not contain ] and end with ]

Upvotes: 0

Jotne
Jotne

Reputation: 41460

Here is an awk

awk '/foo/ {t=1} t {a[++b]=$0} /pattern/ {f=1} /^]/ {if (f) for (i=1;i<=b;i++) print a[i];delete a;b=t=f=0}' file

Example data

cat file
foo
data
more
]
foo
...
bar
...
pattern
...
]
more
foo
here
yes
]
end

Test with awk

awk '/foo/ {t=1} t {a[++b]=$0} /pattern/ {f=1} /^]/ {if (f) for (i=1;i<=b;i++) print a[i];delete a;b=t=f=0}'
foo
...
bar
...
pattern
...
]

Some more easy to read:

awk '
/foo/ {t=1} 
t {a[++b]=$0} 
/pattern/ {f=1} 
/^]/ {if (f) 
    for (i=1;i<=b;i++) 
        print a[i]
    delete a
    b=t=f=0
    }
'

Test if foo is found, set t to true
If t is true, store all line in array a
If pattern is found, set flag f
If ] is found, test if flag f is true, then print array a Reset every ting and start over.

Upvotes: 1

Wintermute
Wintermute

Reputation: 44063

Soo...you want to print a section between something that matches foo and something that matches ] if it contains something that matches pattern, is that correct? Then

sed -n '/foo/ { :a; N; /\]/!ba /pattern/ p }' filename

The sed code works as follows:

/foo/ {       # if a line matches foo
  :a          # jump label
  N           # fetch the next line and append it to the pattern space
  /\]/! ba    # if the result does not match ] (that is, if the last fetched
              # line does not contain something that matches ]), go back to :a
  /pattern/ p # if in all these lines, there is something that matches the
              # pattern, print them
}

To make the match non-greedy at the front -- that is to say, if in a file

1
foo
2
foo
3
pattern
4
]
5

the match should include 3 and 4 but not 2, the script could be amended like this (or similar, depending on the patterns you want to use):

sed -n '/foo/ { :a; N; /\n[^\n]*foo/ s/.*\n//; /\]/!ba /pattern/ p }' filename

Where /\n[^\n]*foo/ s/.*\n// will remove everything before the last fetched line if something in that line matches foo.

If your patterns are line patterns (i.e., if they contain ^ or $), they will need to be amended. Once there is more than one line in the pattern space, ^ will match the beginning and $ the end of the pattern space, not of a line. You can then use \n to match line endings. For example, if you wanted to match non-greedily between lines that are exactly foo and ] if there's a line between them that is exactly pattern, you could use

sed -n '/^foo$/ { :a; N; /\nfoo$/ s/.*\n//; /\n\]$/!ba /\npattern\n/ p }' filename

Upvotes: 2

Related Questions