Ignore blank variable return from qx - regex

I'm having difficulty with one little bit of my code.
open ("files","$list");
while (my $sort = <files>) {
chomp $sort;
foreach my $key (sort keys %ips) {
if ($key =~ $sort) {
print "key $key\n";
my $match =qx(iptables -nL | grep $key 2>&1);
print "Match Results $match\n";
chomp $match;
my $banned = $1 if $match =~ (/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/);
print "Banned Results $banned\n";
if ($key =~ $banned) {
print "Already banned $banned\n";
} else {
system ("iptables -A INPUT -s $key -j DROP");
open my $fh, '>>', 'banned.out';
print "Match Found we need to block it $key\n";
print $fh "$key:$timestamp\n";
close $fh;
}
}
}
}
So basically what I'm doing is opening a list of addresses 1 per line.
Next I'm sorting down my key variable from another section of my script and matching it with my list, if it matches then it continues on to the if statement.
Now with that matched key I need to check and see if its blocked already or not, so I'm using a qx to execute iptables and grep for that variable. If it matches everything works perfectly.
If it does not match, in other words my iptables -nL | grep $key returns a blank value instead of moving on to my else statement it "grabs" that blank value for $match and continues to execute.
For the life of me I can't figure out how to strip that blank value out and basically show it as no return.
I know there are modules for iptables etc however I have to keep this script as generic as possible.

The problem is that, when iptables returns no results, $banned is left at its default value of undef. Used as a regex, $banned matches every string, so your condition:
if ($key =~ $banned) {
always matches. I think what you meant to write was probably
if ($key eq $banned) {
which will fail if either $banned is undef (because $matched was empty or didn't match the regex) or if the IP address you pulled out with the regex was somehow different from $key.
If you're confident that the first IP in the iptables result will be the same as $key then you could simplify your condition to just
if ($match =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {

I suggest you put the entire output from iptables -nL into an array and grep it using Perl. That way you will be calling the utility only once, and it is easy to detect an empty list.
If you write
my #iptables = qx(iptables -nL);
at the top of your code, then you can query this output by
my #match = grep /\b$key\b/, #iptables;
and if there are no records that contain the IP address then a subsequent
if (#match) { ... }
will fail.
There are a few other problems with your code. Firstly, you must always use strict and use warnings at the start of your program, and declare all variables at their first point of use. This will uncover many simple errors that you may otherwise easily overlook, and applies especially if you are asking for help with your code.
Your open call should look like
open my $fh, '<', $file or die $!;
together with
while (my $sort = <$fh>) { ... }
And you seem to have missed the point of hashes. There is no need to read through all of the keys in a hash looking for a match, as the hash elements can be accessed directly with $ips{$sort}. If the value returned is undef then the element doesn't exist, or you can explicitly check for its existence with if (exists $ips{$sort}) { ... }.
I cannot help further as I have no access to a platform that provides iptables. If you need more help then please post some output from the utility.

Related

Searching /etc/passwd for username

I'm trying to search the /etc/passwd file for a username that the use unputs but I'm not sure about the if statement syntax. My current code is:
print "Username?";
$username = <STDIN>;
open (PASSWD, '/etc/passwd')
while (<PASSWD>);
if (PASSWD ((split(/:/)[0] =~ $username)) {
print ((split(/:/)[5]), "\n");
}
close (PASSWD);
Something is wrong with the syntax and I'm having difficulty finding the correct way despite searching stackoverflow and google. Any help would be appreciated.
Perl has built-in functions for that, see getpwnam or User::pwent:
use warnings;
use strict;
print "Username? ";
chomp( my $username = <STDIN> );
die "Unknown user $username\n" unless getpwnam($username);
my $dir = (getpwnam($username))[7];
print $dir, "\n";
# - or -
use User::pwent;
print getpwnam($username)->dir, "\n";
I assume the missing semicolon at the end of your open() line is a typo.
Your while statement needs a block, not a semicolon.
while (<PASSWD>) {
... # stuff
}
When you run this line:
$username = <STDIN>;
Then $username will end containing all of the characters the user has typed at the command line. Crucially, that includes the newline character that was generated when they pressed the "Enter" key.
You then go on to compare that variable with the first field from the records in /etc/passwd. Those fields don't contain a newline character, so the match never succeeds.
You'll want to remove the newline from the end of $username. That's what chomp() is for.
chomp($username = <STDIN>);
Also, the PASSWD in your if statement is very strange. I'm not sure why you think it's necessary. It's not.
if ( (split(/:/)[0] =~ $username) {
But actually, a regex check is overkill here. You should be checking for string equality.
if ((split(/:/)[0] eq $username) {
A couple of other tips:
Always use strict and use warnings.
Use lexical variables for filehandles, use the three-argument version of open() and always check the return value from open()
open my $passwd_fh, '<', '/etc/passwd'
or die "Cannot open /etc/passwd: $!\n";

Trying to match two variables that both contain special characters in Perl

So here is a weird problem. I have a ton of scripts that are executed by "master" scripts and I need to verify that what is in the "master" is valid. Problem is, these scripts contain special characters and I need to match them to make sure the "Master" is referencing the correct scripts.
An example of one file might be
Example's of file names (20160517) [test].sh
Here is what my code looks like. #MasterScipt is an array where each element is a filename of what I expect the sub-scripts to be named.
opendir( DURR, $FileLocation ); # I'm looking in a directory where the subscripts reside
foreach ( readdir(DURR) ) {
for ( my $j = 0; $j != $MasterScriptlength; $j++ ) {
$MasterScipt[$j] =~ s/\r//g;
print "DARE TO COMPARE\n";
print "$MasterScipt[$j]\n";
print "$_\n";
#I added the \Q to quotemeta, but I think the issue is with $_
#I've tried variations like
#if(quotemeta($_) =~/\Q$MasterScipt[$j]/){
#To no avail, I also tried using eq operator and no luck :(
if ( $_ =~ /\Q$MasterScipt[$j]/ ) {
print "WE GOOD VINCENT\n";
}
}
}
closedir(DURR);
No matter what I seem to do, my output will always look like this
DARE TO COMPARE
Example's of file names (20160517) [test].sh
Example's of file names (20160517) [test].sh
OK, I was staring at this thing for too long, and I think writing this question out helped me answer it.
Not only did I need to add \Q in my regex, but there was a whitespace character. I did a chomp to both $_ and $MasterScipt[$j] and now its working.
I suggest that your code should look more like this. The main changes are that I have used a named variable $file for the values returned by readdir, and I iterate over the contents of the array #MasterScipt instead of its indexes because $j is never used in your own code except to access the array elements
s/\s+\z// for #MasterScipt;
opendir DURR, $FileLocation or die qq{Unable to open directory "$FileLocation": $!};
while ( my $file = readdir DURR ) {
for my $pattern ( #MasterScipt ) {
print "DARE TO COMPARE\n";
print "$pattern\n";
print "$file\n";
if ( $file =~ /\Q$pattern/ ) {
print "WE GOOD VINCENT\n";
}
}
}
closedir(DURR);
But this is a simple grep operation and it can be written as such. This alternative builds a single regular expression that will match any of the items in #MasterScipt and uses grep to build a list of all values returned by readdir that match it
s/\s+\z// for #MasterScipt;
my #matches = do {
my $re = join '|', map quotemeta, #MasterScipt;
opendir my $dh, $FileLocation or die qq{Unable to open directory "$FileLocation": $!};
grep { /$re/ } readdir $dh;
};

Matching fields in a log file and transforming results

First a quick intro. I'm new here, so if I screw up a post, please let me know and I'll fix it.
I've been trying to accomplish my goal using perl, but I'm stuck. I don't need to use perl to accomplish it, but I figure it's that, or Excel and I like perl better. If you have a better method please share.
I start with a file (output from a log file). It is 1 line, fields delimitted by colon. Here is an example of the file:
RmDenySumm:SGID=46244:Req=15000:tsid=46244:AllocBw=38332:BwList=12456/12500/3750/5876/3750:tsid=63042:AllocBw=38750:BwList=15000/12500/3750/3750/3750:tsid=63043:AllocBw=36717:BwList=14706/12500/3750/5761:tsid=63044:AllocBw=37011:BwList=15000/12500/5761/3750:tsid=61741:AllocBw=38450:BwList=12339/3750/6501/12502/3357:tsid=61721:AllocBw=37460:BwList=12500/15000/4200/5760:tsid=2072:AllocBw=31975:BwList=12136/12339/3750/3750:tsid=2073:AllocBw=24260:BwList=14634/5876/3750:tsid=30842:AllocBw=38453:BwList=14634/12500/5761/5557:tsid=30843:AllocBw=37105:BwList=15000/15000/3750/3355:tsid=30844:AllocBw=38295:BwList=14706/12339/3750/3750/3750:tsid=30845:AllocBw=25601:BwList=5762/12339/3750/3750:tsid=30846:AllocBw=38455:BwList=15000/12136/5761/5557:tsid=30847:AllocBw=26974:BwList=14634/12339:tsid=30848:AllocBw=29634:BwList=14634/15000:tsid=30849:AllocBw=37338:BwList=14838/15000/3750/3750:tsid=60958:AllocBw=36898:BwList=12339/12500/6501/5557:tsid=60959:AllocBw=37178:BwList=12339/12500/12339:tsid=60960:AllocBw=27339:BwList=12339/15000:tsid=60962:AllocBw=34839:BwList=12339/3750/15000/3750:tsid=60963:AllocBw=37500:BwList=15000/15000/3750/3750:tsid=60964:AllocBw=38346:BwList=15000/3754/15000/4592:tsid=60965:AllocBw=24626:BwList=15000/5876/3750:tsid=60966:AllocBw=34513:BwList=12502/12500/5761/3750
I need to grab all of "AllocBW=######" fields, separate the number part from the "AllocBW", add them all together then subtract them from a set value.
In perl, I have this:
#!/usr/bin/perl -w
use Data::Dumper;
#
#
my $file = "/home/nick/perl/svcgroup.txt";
my #asplit;
my $c = 0;
open (FILE, "<", $file) or die "Can't open file".$!."\n";
while (<FILE>) {
$_ =~ s/\n//g;
push(#asplit, split (":", $_));
#print Dumper #asplit;
}
foreach $splits (#asplit) {
if ($splits =~ m/AllocBw/) {
print $splits."\n";
}
}
#print Dumper #asplit;
print "\n\n";
close FILE;
exit;
Which leaves me with:
AllocBw=38332
AllocBw=38750
AllocBw=36717
AllocBw=37011
AllocBw=38450
AllocBw=37460
AllocBw=31975
AllocBw=24260
AllocBw=38453
AllocBw=37105
AllocBw=38295
AllocBw=25601
AllocBw=38455
AllocBw=26974
AllocBw=29634
AllocBw=37338
AllocBw=36898
AllocBw=37178
AllocBw=27339
AllocBw=34839
AllocBw=37500
AllocBw=38346
AllocBw=24626
AllocBw=34513
This is where I get stuck. I'm not sure how to strip these values down to the number and add them up.
If someone can assist, I'd be grateful. If this is more easily accomplished using something other than Perl, that's fine too. My programming scope is limited, as I only make small scripts to accomplish small repetitive tasks at work.
EDIT FOR BORODIN
ie (not formatted like this, this is just for illustration):
AllocBw 12575+
AllocBw 12568+
AllocBw 12358 = TotAllocBw 37501
MaxBw 38800*3=116400
116400(MaxBw) - 37501(TotAllocBw) = TotAvaiBw 78899
This would just be a big bonus. The script you wrote works perfectly well for my purposes and I can adapt it as I need. Thanks again! Much appreciated. I was able to follow everything you did differently in the script and learned some new stuff.. Thanks for that as well.
It is simplest to use a global regular expression match to find all occurrences of AllocBw=... in each line of your input file.
This program's outer while loop iterates over all the lines in the input file, and so should be executed only once.
The inner while iterates over all instances of the regex pattern AllocBw=(\d+) (AllocBw= followed by any number of decimal digits) and captures the numeric value into $1.
The captured number is added to $total each time, and can simply be printed at the end.
use strict;
use warnings;
my $file = '/home/nick/perl/svcgroup.txt';
open my $fh, '<', $file or die qq{Unable to open "$file" for input: $!};
my $total = 0;
while ( <$fh> ) {
$total += $1 while /AllocBw=(\d+)/g;
}
printf "Total: %d\n", $total;
output
Total: 826049

Distinguish multiple regex hits in a line?

I'm trying to replace IP-Addresses with random numbers in Perl:
while (my $line = <file>){
$line =~ $regex{'ipadress'};
my $rand0 = int(rand(256));
my $rand1 = int(rand(256));
my $rand2 = int(rand(256));
my $rand3 = int(rand(256));
$& = "$rand0.$rand1.$rand2.$rand3\n";`
}
The problem is that in some cases there are multiple IP-Addresses in one line.
How to avoid that they all get the same random numbers?
Well for a start $& is read-only and you can't assign to it like that to modify the target string.
I'm also unsure whether the key to your hash is really ipadress (with one d) but I'm sure you can fix it if not.
I would write something like this. The /e modifier on the substitute operator causes the replacement string to be executed to determine what to replace the match with. The join statement generates four byte values from 0 to 255 and joins them with dots to form a random address.
while (my $line = <$fh>) {
$line =~ s{$regex{ipadress}}{
join '.', map int(rand(256)), 0..3
}eg;
print $line;
}
This might be helpful:
sub rip { return join(".", map { int(rand(256)) } (1..4) ) }
open my $f, '<', 'input' or die($!);
while (my $line = <$f>){
$line =~ s/$regex{'ipadress'}/rip()/eg;
}
close($f);
These answers are good ways to ensure that new random numbers are picked for each IP address. But the poster's main question is, "How to avoid that they all get the same random numbers?" and it's unclear to me whether they meant "get four random numbers for each IP address in the line" or "guarantee that no two randomly-chosen IP addresses are the same."
In case it's the latter: the probability of getting the same results from four calls of rand(256) twice in a row is one in 232, which seems hardly worth worrying about, but if you are required to guarantee that they're different, you can keep a hash of addresses you've already picked, and update it each time you generate a new address. Stealing from #perreal's solution:
sub rip {
my $picked_addrs = shift;
my $new_addr;
do {
$new_addr = join(".", map { int(rand(256)) } (1..4) );
} while defined($picked_addrs->{$new_addr});
$picked_addrs->{$new_addr} = 1;
return $new_addr;
}
open my $f, '<', 'input' or die($!);
while (my $line = <$f>){
my %picked_addrs;
$line =~ s/$regex{'ipadress'}/rip(\%picked_addrs)/eg;
}
close($f);
If you want to make sure that you never pick the same address twice anywhere in the file, just declare %picked_addrs outside the while loop, so it doesn't get reset for each line:
open my $f, '<', 'input' or die($!);
my %picked_addrs;
while (my $line = <$f>){
$line =~ s/$regex{'ipadress'}/rip(\%picked_addrs)/eg;
}
close($f);

Perl substitution using a hash

open (FH,"report");
read(FH,$text,-s "report");
$fill{"place"} = "Dhahran";
$fill{"wdesc:desc"} = "hot";
$fill{"dayno.days"} = 4;
$text =~ s/%(\w+)%/$fill{$1}/g;
print $text;
This is the content of the "report" template file
"I am giving a course this week in %place%. The weather is %wdesc:desc%
and we're now onto day no %dayno.days%. It's great group of blokes on the
course but the room is like the weather - %wdesc:desc% and it gets hard to
follow late in the day."
For reasons that I won't go into, some of the keys in the hash I'll be using will have dots (.) or colons (:) in them, but the regex stops working for these, so for instance in the example above only %place% gets correctly replaced. By the way, my code is based on this example.
Any help with the regex greatly appreciated, or maybe there's a better approach...
You could loosen it right up and use "any sequence of anything that isn't a %" for the replaceable tokens:
$text =~ s/%([^%]+)%/$fill{$1}/g;
Good answers so far, but you should also decide what you want to do with %foo% if foo isn't a key in the %fill hash. Plausible options are:
Replace it with an empty string (that's what the current solutions do, since undef is treated as an empty string in this context)
Leave it alone, so "%foo%" stays as it is.
Do some kind of error handling, perhaps printing a warning on STDERR, terminating the translation, or inserting an error indicator into the text.
Some other observations, not directly relevant to your question:
You should use the three-argument version of open.
That's not the cleanest way to read an entire file into a string. For that matter, for what you're doing you might as well process the input one line at a time.
Here's how I might do it (this version leaves unrecognized "%foo%" strings alone):
#!/usr/bin/perl
use strict;
use warnings;
my %fill = ( place => 'Dhahran',
'wdesc:desc' => 'hot',
'dayno.days' => 4 );
my $filename = 'report';
open my $FH,,'<', $filename or die "$filename: $!\n";
while (my $line = <$FH>) {
foreach my $key (keys %fill) {
$line =~ s/\Q%$key%/$fill{$key}/g;
}
print $line;
}
And here's a version that dies with an error message if there's an unrecognized key:
#!/usr/bin/perl
use strict;
use warnings;
my %fill = ( place => 'Dhahran',
'wdesc:desc' => 'hot',
'dayno.days' => 4 );
my $filename = 'report';
open my $FH,,'<', $filename or die "$filename: $!\n";
while (my $line = <$FH>) {
$line =~ s/%([^%]*)%/Replacement($1)/eg;
print $line;
}
sub Replacement {
my($key) = #_;
if (exists $fill{$key}) {
return $fill{$key};
}
else {
die "Unrecognized key \"$key\" on line $.\n";
}
}
http://codepad.org/G0WEDNyH
$text =~ s/%([a-zA-Z0-9_\.\:]+)%/$fill{$1}/g;
By default \w equates to [a-zA-Z0-9_], so you'll need to add in the \. and \:.