tags:

views:

595

answers:

6

I am trying to expand the string $searchCriteria in the if condition. Any clues?

use strict;

my $argNum;
my $searchCriteria = "";
foreach $argNum (0 .. $#ARGV) {
    $searchCriteria = $searchCriteria . "(\$_ =~ \/" . $ARGV[$argNum] . "\/i) && ";
}
$searchCriteria =~ s/&& $//; 
#print $searchCriteria;

open IP, "<vm.txt" or die $!;

my @fileContents = <IP>;
foreach (@fileContents) {
    if (${$searchCriteria}) {
        print $_;
    }
}
+1  A: 

Uhm... I'm not sure what the question is... but somehow I suspect that you need the qr// expression (quoted regex).

chaos
A: 

What's your question? Perhaps you are looking for ack? It seems like you are attempting to eval a string (usually a bad idea). You should at least use qr//. Maybe something like:

my @regexs = map qr/(?i)$_/, @ARGV;
my $search = sub {
  for my $re (@regexes) {
    return unless /$re/;
  }
  return 1;
}
open IP, "<", "vm.txt" or die "Err: $!";
while (<IP>) {
  print $_ if $search->();
}

This could easily be improved. And it's not tested. YMMV.

runrig
+3  A: 

If your search criteria are regular expressions, you should prepare your own compiled regexp. Also note the use of the while loop (when reading the file) to avoid excessive memory use. If you want lines containing any of argument:

use strict;
use warnings;

my $searchRe = do {
  my $searchCriteria = join '|', map "(?:$_)", @ARGV;
  qr/$searchCriteria/i;
};

open my $fh, '<', 'vm.txt' or die $!;

while (<$fh>) {
  print if m/$searchRe/;
}

close $fh;

or if you want lines containing all ones:

use strict;
use warnings;

my $searcher = do {
  my @searchCriteria = map qr/$_/i, @ARGV;
  sub {
    # study; # study can help for long lines or lot of regular expressions
    for my $re (@searchCriteria) {
      return unless m/$re/;
    }
    return 1
  }
};

open my $fh, '<', 'vm.txt' or die $!;

while (<$fh>) {
  print if $searcher->();
}

close $fh;

(Note that you might want a \Q and \E around the $_ if the command-line arguments are strings rather than regular expressions.)

Finally, if you want improve speed for many search criteria use Regexp::Optimizer.

use Regexp::Optimizer;

my $searchRe = do {
  my $searchCriteria = join '|', map "(?:$_)", @ARGV;
  Regexp::Optimizer->new->optimize(qr($searchCriteria)i);
};
Hynek -Pichi- Vychodil
FWIW, I think Perl's regex engine handles all of those optimizationsautomatically now.
jrockway
Axeman
A beautiful, beautiful translation of the posted question's mess. Kudos.
Robert P
Except it doesn't quite solve the question as clarified in the comments: he wants to see if *all* the values are on the line. :)
Robert P
@jrockway: I can't find it in 5.10. I remember in older versions of perl that it doesn't do this sort of optimization and this fact was mentioned in documentation. May be you right and Regexp::Optimizer is useless now.
Hynek -Pichi- Vychodil
+1  A: 

It looks like you want to eval whether or not the line contains ALL arguments in @ARGV.

I think this might be more along the lines of what you're looking for, and more palatable to the perl crowd as well.

use FileHandle;
use List::MoreUtil qw<all>;

my @regexes = map { qr/\L$_\E/i } @ARGV;

my $fh = FileHandle->new( '<vm.txt' );
die "Err: $!" unless $fh;

foreach my $line ( <$fh> ) { 
    print $line if all { $line =~ m/$_/i } @regexes;
}

Of course if you wanted some compression because you're going to execute the test time and time again in a single execution, you could create a sub for that.

sub create_compound_test { 
    my $test_sub_text = "sub (_) {\n    local \$_ = shift;";

    foreach my $arg ( map { lc; } @_ ) { 
        $test_sub_text .= "\n    return unless m/$arg/i;";
    }
    $test_sub_text .= "\n    return 1;\n}";

    my $test_sub = eval $test_sub_text;
    Carp::croak "Could not create test:\n $test_sub_text - $@" if $@;
    return $test_sub;
}

It does the same thing as $_ =~ /blah/i && $_ =~ /blah2/ ...

Then it would be:

my $match_all = create_compound_test( @ARGV );
foreach ( <$fh> ) { 
    print if $match_all->();
}

But I would probably more likely do this:

my $match_all = create_compound_test( @ARGV );
foreach ( grep { $match_all->(); } <$fh> ) { 
    print;
}

...or even...

print foreach grep { $match_all->(); } <$fh>;
Axeman
A: 

You could construct a single regex that would work IF the arguments were not regex expressions.

The idea is the following sequence:

  1. an alternation (one|two|...)
  2. followed by non-greedy anything (.*?)
  3. followed by a negative lookahead of what matched alternation #1 (?!\1)
  4. followed by the repeated alternation (one|two|...)
  5. followed by non-greedy anything (.*?)
  6. followed by a negative lookahead of an alternation of what matched either of the last alternations (?!\1|\2)

Repeat 4-6, until we have a match for all terms.

So the code that constructs this is:

sub create_compound_match { 

    my @args          = @_;
    my $arg_limit     = $#args; 
    my $expr          = join( '|', map { lc; } @args );
    my $neg_lookahead = join( '|', map { "\\$_" } 1..$arg_limit );

    my $test = join( '.*?'
                   , "($expr)"
                   , ( map { '(?!' 
                           . substr( $neg_lookahead, 0, 3 * $_ - 1 ) 
                           . ")($expr)" 
                           } 1..$arg_limit 
                      )
                   );

    return qr/$test/i;
}

Which would be tested in this fashion:

my $test = create_compound_match( @ARGV );
print foreach grep { /$test/i } <$fh>;

However, this lacks the ability of to pass in regexes on the command line @ARGV could equal qw, and the generated regex would happily match 'a aa aaa aaaa' without requiring the match of b*, because "aa" != "a", so the negative lookahead is satisfied.

Axeman
A: 

After looking at Taking from Hynek -Pichi- Vychodil's answer I realized you could greatly simplify this entire task with List::MoreUtil's all subroutine. I highly recommend this package.

use strict;
use warinings;
use List::MoreUtil qw/all/; # if installed (not a bad idea to get it if not)

my @regexen = map { qr/$_/i } @ARGV;

open my $fh, '<', 'vm.txt' or die $!;

foreach my $line (<$fh>) { 
    print $line if all { $line =~ $_ } @regexen;
};

close $fh;

Subsitute all with any for an "or" based match.

Edit: Damn, looks like Axeman posted something very similar to this. Great minds think alike, eh? :)

Robert P