Nested Foreach and Closure

M

mt35

Hello,

I'm trying to pass a lexical variable down from one subroutine to
another. I have read perlfaq7 on closure (among others) and grasp the
concept for the situation they gave, however I think I'm trying to do
something else entirely. Also I'm only two weeks old in perl so if I'm
going about this in totally the wrong way pointers on the right way to
go are appreciated.

I have an array of an ipfilter log (@fwlog) and a separate array of 10
addresses (@hirate), each of which will have an entry in the ipfilter
log more than 15 times. I want to make an additional array (@hirate1)
of the full log entry foreach address, thus weeding out all entries
that do not correspond to the 10 address array (@hirate).

The original code with no closure performed on it:


foreach (@hirate) {
$y = $_;
foreach (@fwlog) {
push (@hirate1, $_) if ($_ =~ $y);
}
}

Example Variables:
$y = 192.168.1.81
@fwlog = Jan 4 03:01:02 host0 ipmon[51]: 03:01:01.088578 xl0 @0:1 b
192.168.1.81,1047 -> 192.168.1.100,53 PR udp len 20 61 OUT


The original problem I was having was @hirate1 would come out with a
count of approx. 3000 when the original firewall log had a count of
approx 1900.

Here is what I tried:

foreach (@hirate) {
$y = $_;
$q = max($y);
&$q();

sub max {
return sub {
foreach (@fwlog) {
push (@hirate1, $_) if ($_ =~ $y);
}
}
}
}

However, this brings back approx 3000 entires as well? Do I have some
syntax wrong or am I way off point?


Thanks,

-SLM
 
B

Ben Morrow

Quoth (e-mail address removed) (mt35):
Hello,

I'm trying to pass a lexical variable down from one subroutine to
another. I have read perlfaq7 on closure (among others) and grasp the
concept for the situation they gave, however I think I'm trying to do
something else entirely. Also I'm only two weeks old in perl so if I'm
going about this in totally the wrong way pointers on the right way to
go are appreciated.

I have an array of an ipfilter log (@fwlog) and a separate array of 10
addresses (@hirate), each of which will have an entry in the ipfilter
log more than 15 times. I want to make an additional array (@hirate1)
of the full log entry foreach address, thus weeding out all entries
that do not correspond to the 10 address array (@hirate).

The original code with no closure performed on it:

If you are trying to use closures you *certainly* need to be using
lexical variables. In any case, you should have

use strict;
use warnings;

at the top of your program.

my @hirate1;
foreach (@hirate) {
$y = $_;

for my $y (@hirate) {
foreach (@fwlog) {
push (@hirate1, $_) if ($_ =~ $y);

push @hirate1, $_ if /$y/;
}
}

Example Variables:
$y = 192.168.1.81
@fwlog = Jan 4 03:01:02 host0 ipmon[51]: 03:01:01.088578 xl0 @0:1 b
192.168.1.81,1047 -> 192.168.1.100,53 PR udp len 20 61 OUT

The original problem I was having was @hirate1 would come out with a
count of approx. 3000 when the original firewall log had a count of
approx 1900.

Obviously you are ending up with entries from @fwlog in @hirate1 more
than once; run it with a small data set to see.
Here is what I tried:

foreach (@hirate) {
$y = $_;
$q = max($y);
&$q();

sub max {
return sub {
foreach (@fwlog) {
push (@hirate1, $_) if ($_ =~ $y);
}
}
}
}

However, this brings back approx 3000 entires as well? Do I have some
syntax wrong or am I way off point?

I'm not quite sure what you expected this to achieve... some points
unrelated to your problem:

You should not define (named) subs inside loops.
Your sub name 'max' is... confusing.
You should avoid global variables where you don't need them.
Your 'foreach (@fwlog)' loop is what grep is designed to do.

So you could rewrite as:

sub look_for {
my $y = shift;
return sub {
return grep /$y/, @_;
};
}

my @hirate1;
for (@hirate) {
push @hirate1, look_for($_)->(@fwlog);
}

or use map:

my @hirate1 = map { look_for($_)->(@fwlog) } @hirate;

However, there is no point in having a closure here: you might as well
have simply written:

sub look_for {
my $y = shift;
return grep /$y/, @_;
}

my @hirate1 = map { look_for $_, @fwlog } @hirate;

at which point it becomes clear that look_for is simply grep.

I would code this problem like this:

# create a regex that matches any entry in @hirate
my $hirates = join '|', map { qr/\Q$_/ } @hirate;

# find the entries in the log that match
my @hirate1 = grep /$hirates/, @fwlog;

which won't return any entry in @fwlog more than once.

Ben
 
J

John W. Krahn

mt35 said:
I'm trying to pass a lexical variable down from one subroutine to
another. I have read perlfaq7 on closure (among others) and grasp the
concept for the situation they gave, however I think I'm trying to do
something else entirely. Also I'm only two weeks old in perl so if I'm
going about this in totally the wrong way pointers on the right way to
go are appreciated.

I have an array of an ipfilter log (@fwlog) and a separate array of 10
addresses (@hirate), each of which will have an entry in the ipfilter
log more than 15 times. I want to make an additional array (@hirate1)
of the full log entry foreach address, thus weeding out all entries
that do not correspond to the 10 address array (@hirate).

The original code with no closure performed on it:

foreach (@hirate) {
$y = $_;
foreach (@fwlog) {
push (@hirate1, $_) if ($_ =~ $y);
}
}

Example Variables:
$y = 192.168.1.81
@fwlog = Jan 4 03:01:02 host0 ipmon[51]: 03:01:01.088578 xl0 @0:1 b
192.168.1.81,1047 -> 192.168.1.100,53 PR udp len 20 61 OUT

The original problem I was having was @hirate1 would come out with a
count of approx. 3000 when the original firewall log had a count of
approx 1900.

Here is what I tried:

foreach (@hirate) {
$y = $_;
$q = max($y);
&$q();

sub max {
return sub {
foreach (@fwlog) {
push (@hirate1, $_) if ($_ =~ $y);
}
}
}
}

However, this brings back approx 3000 entires as well? Do I have some
syntax wrong or am I way off point?

One problem that I can see is that you need boundaries defined in the
regular expression. In other words, the expression /10.98.2.4/ will
match '110.98.2.45', etc. Perhaps something like this will work
(untested):

my @hirate1;

LINE:
for my $line ( @fwlog ) {
for my $ip ( @hirate ) {
if ( $line =~ /\b$ip\b/ ) {
push @hirate, $line;
next LINE;
}
}
}



John
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,156
Messages
2,570,878
Members
47,404
Latest member
PerryRutt

Latest Threads

Top