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.
我正在开发一个Perl脚本,用于帮助我们网络上的机器扫描自动化。我不是一个贸易程序员,但是这个项目已经分配给我,而且我很难过。在我解释困扰我的本质之前,让我解释一下我在做什么的大纲。
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.
基本上,这个脚本每n小时运行一次。运行时,它将检查包含活动IP日志的文件,并根据DHCP日志检查它们,以仅列出那些静态的日志。然后将这些放入哈希(如果标记为初始化则为新的,否则使用Storable加载),密钥为IP,并且在数组内,其MAC [0]和“最后扫描”日期[1]最初设置为脚本的下一部分将比较今天的日期和“上次扫描”日期之间的日期 - 如果它在某个阈值之下,它会向我们的扫描仪发送查询。
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
将IP / MAC添加到哈希的代码
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
检查日期的代码(先前设置,今天为20120726)。在上面的代码和以下代码之间只有评论
$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.
现在,我认为在进入循环之前更改了值的原因是我在'if(($ date ...){'日期之前添加了'print $ lastScanned'语句,打印日期分配的内容早于$ date - 但如果我要注释'$ SCAN {$ key} [1] = $ date;'语句,print语句将打印'19700101'日期,一切都按预期运行。发生了什么?除了上面显示的两个地方外,$ SCAN {$ key} [1]永远不会被触及。
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!
2 个解决方案
#1
8
Because your @data
array is global, every time your execute the statement
因为@data数组是全局的,所以每次执行语句
$SCAN{$key} = \@data;
you're assigning to $SCAN{$key}
a reference to the same @data
array. Thus, all the values in %SCAN
end up pointing to the same array, which is presumably not what you want.
你正在为$ SCAN {$ key}分配对同一个@data数组的引用。因此,%SCAN中的所有值最终都指向同一个数组,这可能不是您想要的。
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 @data
array to $SCAN{$key}
, by changing the above line to
有几种方法可以解决这个问题。也许最简单的方法是通过将上面的行改为上面的代码,使代码将@data数组的副本分配给$ SCAN {$ key}。
$SCAN{$key} = [ @data ];
Alternatively, you could rewrite the entire loop to use a lexical array declared with my
inside the loop — that way you create a new separate array on each iteration:
或者,你可以重写整个循环来使用我在循环中声明的词法数组 - 这样你就可以在每次迭代时创建一个新的独立数组:
foreach $key (keys %IPS) {
$unsavedDB = 1;
my @data; # <--- this line is new!
$data[0] = $IPS{$key};
$data[1] = 19700101;
print $data[1];
$SCAN{$key} = \@data;
}
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.
但是,你真正应该做的,而不仅仅是修复这个特定bug的症状,就是要了解变量作用域如何在Perl中运行以及如何使用它,并相应地重写你的代码。
In particular, looking at your code, I very much suspect that you're not using the strict
pragma 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:
特别是,查看代码时,我非常怀疑您在代码中没有使用严格的编译指示。如果你想编写干净的Perl代码,你真正应该做的第一件事就是在#!之后立即将以下两行添加到你的所有脚本中!线:
use strict;
use warnings;
The strict
pragma forces you to avoid certain bad and error-prone habits, such as using symbolic references or undeclared global variables, while the warnings
pragma 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).
严格的pragma强迫你避免某些不良和容易出错的习惯,比如使用符号引用或未声明的全局变量,而警告语用语使得解释器会警告你各种其他愚蠢,冒险,模糊或其他不受欢迎的事情(你真的应该视为错误并修复,直到你不再收到警告)。
Of course, this doesn't mean you should just declare all your variables at the beginning of the script with my
(or our
) just to make strict
happy. 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 in
当然,这并不意味着你应该在脚本的开头用my(或我们的)声明所有变量,只是为了让你严格高兴。相反,你应该做的是查看每个变量,看看它实际使用的位置,并在需要的最里面的范围内声明它。(如果你在代码的不同部分重用相同的变量名,那么将它们视为单独的变量并分别声明每个变量。)请记住,您可以在循环语句中声明循环变量,如
foreach my $key (keys %IPS) {
or
while (my $line = <>) {
Ps. I also noticed a worrying comment in the code you showed us:
PS。我还注意到你向我们展示的代码中有一个令人担忧的评论:
# repeat of the above code, but with ...
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
当然,有一些非常非常罕见的场合,你需要以两种不同的方式做同样的事情,但是这里有许多小的和随意的差异,以至于将整个事情写两次更清晰。但如果在这种情况下我会感到非常惊讶 - 我打赌你只能编写一次代码,并且可能只是插入一个
if (not $init and exists ...) {
check at a suitable location.
检查在合适的位置。
#2
3
As Ilmari says, your problem is that every element of %SCAN
points to the same two-element array that was @data
in the first code block, so $SCAN{<anything>}[1]
is the same variable for all the IP addresses.
正如Ilmari所说,你的问题是%SCAN的每个元素都指向第一个代码块中相同的双元素数组@data,因此$ SCAN {
To fix this, my preference would be to forget about @data
and write
要解决这个问题,我的偏好是忘记@data并写
$SCAN{$key} = [ $IPS{$key}, '19700101' ];
which generates a new anonymous array each time the statement is executed and assigns a reference to it as the hash's value.
每次执行语句时都会生成一个新的匿名数组,并将其作为哈希值分配给它。
Note also that I have used a string for the date, as you cannot write things like $date - $lastScanned
: date arithmetic is more complex than that. Subtracting 31-JAN-2012
from 1-FEB-2012
would become 20120201 - 20120131
or 70!
还要注意我已经使用了一个字符串作为日期,因为你不能写像$ date这样的东西 - $ lastScanned:日期算术比这更复杂。从1-FEB-2012减去31-JAN-2012将成为20120201 - 20120131或70!
Fortunately there are modules to make this easier and you can use the module Time::Piece
, which is a core module (i.e. it gets installed with standard Perl since Perl v5.9) and will let you do just this sort of arithmetic.
幸运的是,有一些模块可以使这更容易,你可以使用模块Time :: Piece,它是一个核心模块(即它从Perl v5.9开始安装标准的Perl)并且可以让你做这种算法。
At the top of your program, after use strict
and use warnings
, you write
在程序的顶部,在使用strict和使用警告之后,你写
use Time::Piece;
and then later on, for your initial time, write
然后,在你的初始时间,写
my $initial = localtime(0);
and then
my $date = localtime;
You can see the dates the two values correspond to by just printing them
您可以通过打印它们来查看两个值对应的日期
print $initial, "\n";
print $date, "\n";
which will show something like
这将显示类似的东西
Thu Jan 1 00:00:00 1970
Fri Jul 27 01:40:53 2012
and a simple subtraction gives you the real difference, in seconds
并且简单的减法可以在几秒钟内为您提供真正的差异
print $date - $initial;
So if $threshold
is in days you can check the interval by writing
因此,如果$ threshold是以天为单位,您可以通过写入来检查间隔
if ( $date - $lastScanned > $threshold * 24 * 60 * 60 ) { ... }
I hope I haven't scared you here, but it needed changing and I thought you should know. The module will do a lot more than this, and if you want to look at the documentation it's here. And please ask another question if you get stuck.
我希望我没有在这里吓到你,但它需要改变,我想你应该知道。该模块将做更多的事情,如果你想查看它在这里的文档。如果你遇到困难,请问另一个问题。
#1
8
Because your @data
array is global, every time your execute the statement
因为@data数组是全局的,所以每次执行语句
$SCAN{$key} = \@data;
you're assigning to $SCAN{$key}
a reference to the same @data
array. Thus, all the values in %SCAN
end up pointing to the same array, which is presumably not what you want.
你正在为$ SCAN {$ key}分配对同一个@data数组的引用。因此,%SCAN中的所有值最终都指向同一个数组,这可能不是您想要的。
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 @data
array to $SCAN{$key}
, by changing the above line to
有几种方法可以解决这个问题。也许最简单的方法是通过将上面的行改为上面的代码,使代码将@data数组的副本分配给$ SCAN {$ key}。
$SCAN{$key} = [ @data ];
Alternatively, you could rewrite the entire loop to use a lexical array declared with my
inside the loop — that way you create a new separate array on each iteration:
或者,你可以重写整个循环来使用我在循环中声明的词法数组 - 这样你就可以在每次迭代时创建一个新的独立数组:
foreach $key (keys %IPS) {
$unsavedDB = 1;
my @data; # <--- this line is new!
$data[0] = $IPS{$key};
$data[1] = 19700101;
print $data[1];
$SCAN{$key} = \@data;
}
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.
但是,你真正应该做的,而不仅仅是修复这个特定bug的症状,就是要了解变量作用域如何在Perl中运行以及如何使用它,并相应地重写你的代码。
In particular, looking at your code, I very much suspect that you're not using the strict
pragma 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:
特别是,查看代码时,我非常怀疑您在代码中没有使用严格的编译指示。如果你想编写干净的Perl代码,你真正应该做的第一件事就是在#!之后立即将以下两行添加到你的所有脚本中!线:
use strict;
use warnings;
The strict
pragma forces you to avoid certain bad and error-prone habits, such as using symbolic references or undeclared global variables, while the warnings
pragma 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).
严格的pragma强迫你避免某些不良和容易出错的习惯,比如使用符号引用或未声明的全局变量,而警告语用语使得解释器会警告你各种其他愚蠢,冒险,模糊或其他不受欢迎的事情(你真的应该视为错误并修复,直到你不再收到警告)。
Of course, this doesn't mean you should just declare all your variables at the beginning of the script with my
(or our
) just to make strict
happy. 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 in
当然,这并不意味着你应该在脚本的开头用my(或我们的)声明所有变量,只是为了让你严格高兴。相反,你应该做的是查看每个变量,看看它实际使用的位置,并在需要的最里面的范围内声明它。(如果你在代码的不同部分重用相同的变量名,那么将它们视为单独的变量并分别声明每个变量。)请记住,您可以在循环语句中声明循环变量,如
foreach my $key (keys %IPS) {
or
while (my $line = <>) {
Ps. I also noticed a worrying comment in the code you showed us:
PS。我还注意到你向我们展示的代码中有一个令人担忧的评论:
# repeat of the above code, but with ...
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
当然,有一些非常非常罕见的场合,你需要以两种不同的方式做同样的事情,但是这里有许多小的和随意的差异,以至于将整个事情写两次更清晰。但如果在这种情况下我会感到非常惊讶 - 我打赌你只能编写一次代码,并且可能只是插入一个
if (not $init and exists ...) {
check at a suitable location.
检查在合适的位置。
#2
3
As Ilmari says, your problem is that every element of %SCAN
points to the same two-element array that was @data
in the first code block, so $SCAN{<anything>}[1]
is the same variable for all the IP addresses.
正如Ilmari所说,你的问题是%SCAN的每个元素都指向第一个代码块中相同的双元素数组@data,因此$ SCAN {
To fix this, my preference would be to forget about @data
and write
要解决这个问题,我的偏好是忘记@data并写
$SCAN{$key} = [ $IPS{$key}, '19700101' ];
which generates a new anonymous array each time the statement is executed and assigns a reference to it as the hash's value.
每次执行语句时都会生成一个新的匿名数组,并将其作为哈希值分配给它。
Note also that I have used a string for the date, as you cannot write things like $date - $lastScanned
: date arithmetic is more complex than that. Subtracting 31-JAN-2012
from 1-FEB-2012
would become 20120201 - 20120131
or 70!
还要注意我已经使用了一个字符串作为日期,因为你不能写像$ date这样的东西 - $ lastScanned:日期算术比这更复杂。从1-FEB-2012减去31-JAN-2012将成为20120201 - 20120131或70!
Fortunately there are modules to make this easier and you can use the module Time::Piece
, which is a core module (i.e. it gets installed with standard Perl since Perl v5.9) and will let you do just this sort of arithmetic.
幸运的是,有一些模块可以使这更容易,你可以使用模块Time :: Piece,它是一个核心模块(即它从Perl v5.9开始安装标准的Perl)并且可以让你做这种算法。
At the top of your program, after use strict
and use warnings
, you write
在程序的顶部,在使用strict和使用警告之后,你写
use Time::Piece;
and then later on, for your initial time, write
然后,在你的初始时间,写
my $initial = localtime(0);
and then
my $date = localtime;
You can see the dates the two values correspond to by just printing them
您可以通过打印它们来查看两个值对应的日期
print $initial, "\n";
print $date, "\n";
which will show something like
这将显示类似的东西
Thu Jan 1 00:00:00 1970
Fri Jul 27 01:40:53 2012
and a simple subtraction gives you the real difference, in seconds
并且简单的减法可以在几秒钟内为您提供真正的差异
print $date - $initial;
So if $threshold
is in days you can check the interval by writing
因此,如果$ threshold是以天为单位,您可以通过写入来检查间隔
if ( $date - $lastScanned > $threshold * 24 * 60 * 60 ) { ... }
I hope I haven't scared you here, but it needed changing and I thought you should know. The module will do a lot more than this, and if you want to look at the documentation it's here. And please ask another question if you get stuck.
我希望我没有在这里吓到你,但它需要改变,我想你应该知道。该模块将做更多的事情,如果你想查看它在这里的文档。如果你遇到困难,请问另一个问题。