I am working on a Perl script that is to help with the automation of scanning of machines on our network. I am not a programmer by trade, but none-the-less this project has been assigned to me, and I am quite stumped. Before I explain the nature of what is stumping me, let me explain the outline of what I am doing.
Basically, this script will be run every n hours. When run, it will check a file that holds a log of active IPs and check them against a DHCP log to single out only those that are static. These then are put into a hash (a new one if flagged to initialize, loaded using Storable otherwise), with the key being the IP and within an array their MAC [0] and a “last scanned” date [1] initially set to 19700101. The next part of the script compares the date between today’s date and the “last scanned” date – and if it under a certain threshold, it sends a query to our scanner.
The issue that has me so lost is that when the date is being checked, it seems to me that the date value (the updated “last scanned”) is being set before entering the conditional. While this doesn’t seem likely to me, it is the only possibility I can think of. Here is the relevant chunks of code:
The code that adds IP/MACs to the hash
if(init == 1){
%SCAN = ();
@data = ();
foreach $key (keys %IPS){
$unsavedDB = 1;
$data[0] = $IPS{$key};
$data[1] = 19700101;
print $data[1];
$SCAN{$key} = \@data;
}
}else{
#repeat of the above code, but with a if(exists...) to prevent duplicates from being added to the hash that is loaded via storables.
}
The code that checks the date (which is set previously, and would be 20120726 for today). Between the above code and the following there is nothing but comments
$scanned = 0;
foreach $key (keys %SCAN){
$lastScanned = $SCAN{$key}[1];
if(($date - $lastScanned) > $threshold){
$unsavedDB = 1;
$toScan = ${$key}[0];
#omitted data for security reasons, just basically forms a string to send to a scanner
$SCAN{$key}[1] = $date;
$scanned++;
}
}
print "finished. $scanned hosts queued\n";
Now, the reason that I believe that the value is being changed before entering the loop is when I add a ‘print $lastScanned’ statement right before the ‘if(($date…){‘ the date printed the whatever is assigned to $date earlier – but if I to comment out the ‘$SCAN{$key}[1] = $date;’ statement, the print statements will print the ‘19700101’ date and everything functions as it should. What is happening? $SCAN{$key}[1] is never being touched except in the two places shown above.
Sorry if this is very badly phrased, or doesn’t make sense. I tried my best to explain something that has been stumping me for hours.
Thank you!
Because your
@dataarray is global, every time your execute the statementyou’re assigning to
$SCAN{$key}a reference to the same@dataarray. Thus, all the values in%SCANend up pointing to the same array, which is presumably not what you want.There are several ways in which you could fix that. Perhaps the simplest would be to make the code assign a reference to a copy of the
@dataarray to$SCAN{$key}, by changing the above line toAlternatively, you could rewrite the entire loop to use a lexical array declared with
myinside the loop — that way you create a new separate array on each iteration:However, what you really should do, instead of just fixing the symptoms of this particular bug, is learn how variable scoping works in Perl and how it should be used, and rewrite your code accordingly.
In particular, looking at your code, I very much suspect that you’re not using the
strictpragma in your code. If you want to write clean Perl code, the first thing you really should do is prepend the following two lines to all your scripts, immediately after the#!line:The
strictpragma forces you to avoid certain bad and error-prone habits, such as using symbolic references or undeclared global variables, while thewarningspragma make the interpreter warn you about various other silly, risky, ambiguous or otherwise undesirable things (which you really should treat as errors and fix until you get no more warnings).Of course, this doesn’t mean you should just declare all your variables at the beginning of the script with
my(orour) just to makestricthappy. Instead, what you should do is look at each variable, see where it’s actually used, and declare it in the innermost scope it’s needed in. (If you’re reusing the same variable name in different parts of the code, treat those as separate variables and declare each of them separately.) Remember that you can declare loop variables in the loop statement, as inor
Ps. I also noticed a worrying comment in the code you showed us:
Generally, that kind of code duplication should be a big flashing signal that you’re probably doing something wrong — the golden rule of programming is “Don’t repeat yourself.“
Of course, there are those few, very very rare occasions where you do need to do essentially the same thing in two different ways, but with so many small and arbitrary differences peppered throughout that it’s cleaner to write the whole thing twice. But I’d be very surprised if that was the case here — I bet you could write that code only once, and just maybe insert an
check at a suitable location.