perlisfun has asked for the wisdom of the Perl Monks concerning the following question:

I used to use linux a lot. I liked its grep command which has -A and -B option. -A <line_numbers> gives number of lines after the matching, -B <line_numbers> gives number of lines before the matching.

Right now, i am working on Solaris. Its grep does not have these features. I can write a program to achieve the goal. Actually i really don't want to reinvent the weel. Can some one shed some light on this with perl module or some other perl shortcut? Thanks,

PerlIsFun

Updated 2003-06-11 by mirod: changed title and added formating.

  • Comment on Emulating GNU grep -A and -B switches with perl (was: linux grep feature)

Replies are listed 'Best First'.
Re: linux grep feature
by hardburn (Abbot) on Jun 11, 2003 at 14:28 UTC

    You could probably get GNU grep (external link) to compile on Solaris if you prod it enough.

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    Note: All code is untested, unless otherwise stated

      Alternatively there is http://www.sunfreeware.com/ where a precompiled binary may be found for the appropriate OS/Hardware version.
      /J\
      
(jeffa) Re: linux grep feature
by jeffa (Bishop) on Jun 11, 2003 at 14:54 UTC
    There is already Grep - print matched line and next N lines, and i know there is another one that handles the -B functionality as well as the -A, but i can't find it. Both of these (i think) were written before Tie::File was realeased, here is a version i just slapped together for the fun of it. :) Comments and suggestions are more than welcome.
    use strict; use warnings; use Tie::File; use Getopt::Std; our %opts; getopt('AB',\%opts); die "USAGE: $0 [-An] [-Bn] match file" unless @ARGV; my ($match,$filename) = @ARGV; my @file; tie @file, 'Tie::File', $filename; # i wish there was a way for grep to return indices ... my @found; for (0..$#file) { push @found, $_ if $file[$_] =~ /$match/; } for (@found) { my ($start,$end) = ($_,$_); $start -= $opts{A} if $opts{A}; $end += $opts{B} if $opts{B}; $start = 0 if $start < 0; $end = $#file if $end > $#file; print $_,$/ for @file[$start..$end]; }
    And yes, Perl is fun! :D

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    

      Your code works great for simple cases, but fails for files that have matches that are close together. You end up with duplicate lines being displayed.

      for example try the following:

      grep.pl -A 1 -B 1 test data

      with the file 'data' containing:

      test 1 test 2 test 3

      The results are:

      test 1 test 2 test 1 test 2 test 3 test 2 test 3

      Also your version will parse the entire file before printing out any results. This will be slow for large files.

      Still a good base to start from though...

      Cheers

      Cees

Re: linux grep feature
by fglock (Vicar) on Jun 11, 2003 at 17:51 UTC
Re: linux grep feature
by cees (Curate) on Jun 11, 2003 at 16:56 UTC

    Here is a version that I based heavily on jeffa's version above.

    use strict; use warnings; use Tie::File; use Getopt::Std; our %opts; getopt('AB',\%opts); die "USAGE: $0 [-An] [-Bn] match file" unless @ARGV; my ($match,$filename) = @ARGV; my @file; tie @file, 'Tie::File', $filename, autochomp => 0; my @prebuffer; my $postbuffer_counter = 0; foreach (@file) { if (/$match/) { print @prebuffer, $_; @prebuffer = (); $postbuffer_counter = $opts{A} || 0; } else { if ($postbuffer_counter-- > 0) { print $_ } else { push @prebuffer, $_; shift @prebuffer if @prebuffer > $opts{B}; } } }

    This prints as it finds matches and handles matches that are close together. I haven't done a whole lot of testing, so there very well may be other issues.

    Comments and suggestions are welcome...

    Cees

Re: linux grep feature
by Thelonius (Priest) on Jun 11, 2003 at 17:14 UTC
    Here's a version without Tie::File and with minimal buffering:
    use strict; use warnings; use Getopt::Std; our %opts; getopts('A:B:n',\%opts); die "USAGE: $0 [-n] [-An] [-Bn] pattern [file] ..." unless @ARGV; my $pattern = shift; my @buffer; my @matches; my $nextmatch; my $prevmatch; my $linenum; my $shownames; my $fname; my $showlines = $opts{n}; my $behind = $opts{B} || 0; my $after = $opts{A} || 0; my $bufsize = 1 + $behind; if (@ARGV == 0) { procfile("-"); } else { $shownames = 1 if @ARGV > 1; procfile($fname) while ($fname = shift @ARGV); } sub procfile { $prevmatch = -2 - $after; $linenum = 0; $nextmatch = 0; my $f = shift; unless (open IN, "<$f") { warn "$0: $f: $!\n"; return; } while (<IN>) { push @buffer, $_; my $matched = m/$pattern/o; push @matches, $matched; $linenum++; $nextmatch = $linenum if $matched; if (@buffer >= $bufsize) { proconeline(); } } } proconeline() while @buffer && ($nextmatch || $prevmatch); sub proconeline { my $oldlinenum = $linenum - @buffer + 1; my $oldmatch = shift @matches; $_ = shift @buffer; if ($oldmatch || $oldlinenum - $prevmatch <= $after || $nextmatch) { print "$fname:" if $shownames; print "$oldlinenum:" if $showlines; print; } $prevmatch = $oldlinenum if $oldmatch; $nextmatch = 0 if $oldlinenum == $nextmatch; }