On Tue, Jun 11, 2002 at 04:42:00PM -0400, Ron Newman wrote:
> But this won't solve the original problem, which was how to write a
> Perl script that provides N lines of context before and after a 'grep' match.
> Your program processes each line one at a time, and doesn't store a
> buffer of lines.
I must have been asleep at the keyboard when this question was first posted.
The problem here is that Perl is good at transforming N items
into N items with map, or selecting M items from N items with
grep (where M <= N), but not transforming N items into N+M items.
What you want to use here is an iterator.
To get output like what you'd get from
grep -B1 -C1 '.ab.ac.' /usr/share/dict/words
(one line before and after a set of matches), you need to do
something roughly like the program below (specifically, something
like the closure returned by context_grep()). All the work here
is in defining the iterators themselves, not using them (the last
10 lines of the program). On my computer, the output from this
program is identical to what grep produces, but the Perl version
is *way* slower. :-S
HTH,
Z.
ObPlug: If this is new to you, then beg, borrow or steal your
way into Dominus' tutorial on iterators at OSCon next month.
--------
#!/usr/bin/perl -w
use strict;
sub file_iterator($) {
open(my $fh, shift);
return sub {
return scalar <$fh>;
}
}
sub context_grep(@) {
my %opts = @_;
my $preceding = $opts{pre} || 0;
my $following = $opts{post} || 0;
my $pattern = $opts{pattern};
my $next = $opts{next};
## At the very least, we need a pattern and an iterator
return unless $pattern and $next;
return sub {
my @match;
## Build up the window of preceding lines
while (defined($_ = $next->()) and not m/$pattern/) {
if ($preceding) {
push (@match, $_);
shift (@match) if @match > $preceding;
}
}
return unless $_;
## Build up the window of matched lines
while (defined ($_) and m/$pattern/) {
push(@match, $_);
$_ = $next->();
}
## Build up the window of following lines
my $post = 0;
while (defined($_) and $post < $following) {
push(@match, $_);
$post++;
$_ = $next->();
}
return @match;
}
}
## Now, do the grep with one line before and after each
## set of matches
my $searcher = context_grep(
pre => 1,
post => 1,
pattern => qr(.ab.ac.),
next => file_iterator("/usr/share/dict/words"),
);
my @matches;
push (@matches, join("", @_)) while (@_ = $searcher->());
print join("--\n", @matches);