Reputation: 917
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
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
]
Some key points:
m/.../s
in perl refer to this post:foo((?!foo).)*pattern[^\]]*\]
foo
match the first foo((?!foo).)*
avoid matching foo
in matching part using negative lookaheadpattern
match pattern[^\]]*\]
the following part should not contain ]
and end with ]
Upvotes: 0
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
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