0

I am new to perl and want to emulate grep -n like this:

want:

# egrep -n 'malloc\(|free\(|printf\(' test.c
5:p = malloc(sizeof(char));
6:printf("Test\n");
7:free(p);

have:

# perl grep.pl test.c
malloc\(line 7
free\(line 7
printf(

Processed 10 lines

Script:

#!/usr/bin/perl
$verbose = 1;
@pattern = ('malloc\(', 'free\(', 'printf(');
$counter = 0;
open(FH, "<", $ARGV[1]) or die;

while (<>) {
  my $matches =  (@pattern[0-2]);
  $counter++;
  # print "line $counter:$_" if ($_ =~ /malloc\(/o);
  print join("line $counter\t\n",@pattern),"\n" if ($_ =~ /$matches/o);
  close (FH);
}
print "\n";
$verbose == 1 && print "Processed $counter lines\n";

Somehow the counter is wrong. What am I missing here?

Mat
  • 202,337
  • 40
  • 393
  • 406
Juergen
  • 351
  • 1
  • 2
  • 10
  • 3
    What's the `close(FH)` doing inside that loop? – Mat Jan 14 '12 at 16:41
  • 1
    And what's with `open(FH, "<", $ARGV[1])` in the first place, given that the reading is done from `<>` and not ``? – Jonathan Leffler Jan 14 '12 at 16:57
  • 1
    Why the downvotes? The question is unambiguous and effort has been shown. The only thing that I want to know is why the OP wants to do this in the first place. – Zaid Jan 14 '12 at 19:54

5 Answers5

3

Below is my rudimentary implementation of grep in windows. Perhaps you can tweak it to work for you.

The line numbers in perl are given by $. (see perlvar). When using the diamond operator, it can simply be printed as is. However, if you use more than one file, it will not reset automatically between files, and you will have to do a bit of magic:

while (<>) {
    # grep and print logic goes here
    # e.g. print "$.: $_" if grep_match();

    close ARGV if eof;  # this will reset $. for each file
}

The code:

use strict;
use warnings;
use v5.10;

my $pattern     = shift;

#use re 'debug';
# Refining the regex to allow using modifiers
$pattern =~ s#^/##;
if ( $pattern =~ s{/([^/]+)$}{} ) {
    $pattern = "(?$1)$pattern";
}

# Windows does not allow globbing, this is the fix
for (@ARGV) {
    unless (-e) {
        @ARGV = win_args(@ARGV);
        last;
    }
}

my $found;
while (<>) {
    if (/$pattern/) {
        unless ($found) {
            say "----- $ARGV -----";
        }
        print;
        $found = 1;
    }
    $found = 0 if eof;
}

sub win_args {
    return map glob, @_;
}
TLP
  • 66,756
  • 10
  • 92
  • 149
1

There is a lot wrong with your code, but you should start by adding

use strict;
use warnings;

at the head of the program. You will have to declare all of your variables, but Perl itself will help you solve most trivial problems.

$matches =  (@pattern[0-2]);

is the same as

$matches =  $pattern[-2];

which accesses the second element from the end of the array - 'free(' - and assigns it to $matches, which is not what you want. You could use the pipe operator which matches any of a selection of patterns if you write

$matches = join '|', @pattern;

Also, in

print join("line $counter\t\n",@pattern),"\n" if ($_ =~ /$matches/o);

I don't think join does what you think it does. You are joining all elements of the array with the first string as a delimiter. If you just want the matching line then you need

print "line $counter\t$_\n" if /$matches/o;

That should get you working but, as I say, your program is less than ideal and I have shown the minimum changes necessary.

Borodin
  • 126,100
  • 9
  • 70
  • 144
1

There is a variable named $. that contains the current line number, so just output the value of that variable!

perldoc perlvar
tadmc
  • 3,714
  • 16
  • 14
1

If this is an exercise to get a feel for coding in Perl, the perldoc pages will be helpful to understand how functions are used, files are read and special variables like $. are used. The requirement is well within the language's capabilities.

If this is an attempt to reinvent the wheel, the value-added needs to be clearly laid out.

If this is being done to emulate a *nix command in Windows, aren't you satisfied with whatever is out there already?.

Community
  • 1
  • 1
Zaid
  • 36,680
  • 16
  • 86
  • 155
-1

This should do the trick. Just pass the pattern with the option of a filename (otherwise stdin is used)

#!/usr/bin/perl

use strict;

my $pattern=shift;
my $fileName=shift;

my $fh;

if ($fileName)
{
   open $fh, $fileName or die "Unable to open file $fileName";
}
else
{
   open $fh, '-';
}

my $lineNumber = 1;
while (<$fh>)
{
   print sprintf("%4d %s", $lineNumber, $_) if (m%$pattern%);
   ++$lineNumber;
}

Just construct your pattern as (malloc\()|(free\(')|(printf\()

Ed Heal
  • 59,252
  • 17
  • 87
  • 127
  • nice :) Thank you all! This one looks very clean. Sorry for the odds. – Juergen Jan 14 '12 at 17:15
  • The regex won't compile - you need to escape the parenthesis after 'printf'. Also you haven't answered the question as to why the OP's program doesn't work. – Borodin Jan 14 '12 at 17:24
  • My fault - should have checked and not repied on a cut'n'paste job. As to why it does not work - where do you start? – Ed Heal Jan 14 '12 at 17:27
  • Your file handle juggling is exactly what is already implicitly done when using the diamond operator, and you are doing nothing to improve the functionality, so why reinvent the wheel? – TLP Jan 14 '12 at 17:30
  • ok, didn't know <> can read $1. so here is the cleaner version: http://sprunge.us/WMHQ – Juergen Jan 14 '12 at 17:37
  • @Juergen - instead of foreach you could use `map` – Ed Heal Jan 14 '12 at 17:49