How to get all lines between first and last occurrences of patterns?
sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};'
The sed pattern matching /first/,/second/
reads lines one by one. When some line matches to /first/
it remembers it and looks forward for the first match for the /second/
pattern. In the same time it applies all activities specified for that pattern. After that process starts again and again up to the end of file.
That's not that we need. We need to look up to the last matching of /second/
pattern. Therefore we build construction that looks just for the first entry /foo/
. When found the cycle a
starts. We add new line to the match buffer with N
and check if it matches to the pattern /bar/
. If it does, we just print it and clear the match buffer and janyway jump to the begin of cycle with ba
.
Also we need to delete newline symbol after buffer clean up with /^\n/s/^\n//
. I'm sure there is much better solution, unfortunately it didn't come to my mind.
Hope everything is clear.
I would do it with a little Perl one-liner.
cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)'
A line
like
foo
this
foo
bar
something
something else
foo
bar
and
the
rest
EOF
yields
foo
this
foo
bar
something
something else
foo
bar
Here's a two-pass GNU sed solution that doesn't require much memory:
< infile \
| sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \
| sed -n '1p; $p' \
| tr '\n' ' ' \
| sed 's/ /,/; s/ /p/' \
| sed -n -f - infile
Explanation
- First
sed
invocation passes infile and finds first occurrence offoo
and all subsequent occurrences ofbar
. - These addresses are then shaped into a new
sed
script with two invocations ofsed
and onetr
. Output of the thirdsed
is[start_address],[end_address]p
, without the brackets. - Final invocation of
sed
passes theinfile
again, printing the found addresses and everything in between.