5

我正在编写一个 Perl 脚本,以帮助自动扫描我们网络上的机器。我不是专业的程序员,但是这个项目已经分配给了我,我很困惑。在我解释困扰我的事情的性质之前,让我解释一下我正在做的事情的大纲。

基本上,此脚本将每 n 小时运行一次。运行时,它将检查一个包含活动 IP 日志的文件,并根据 DHCP 日志检查它们以仅挑选出那些是静态的。然后将它们放入哈希中(如果标记为初始化,则使用新的,否则使用 Storable 加载),密钥是 IP,在数组中它们的 MAC [0] 和“最后扫描”日期 [1] 最初设置为19700101。脚本的下一部分比较今天日期和“上次扫描”日期之间的日期 - 如果它低于某个阈值,它会向我们的扫描仪发送查询。

让我如此迷失的问题是,在检查日期时,在我看来,日期值(更新的“上次扫描”)是在输入条件之前设置的。虽然这对我来说似乎不太可能,但这是我能想到的唯一可能性。以下是相关的代码块:

将 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.
 }

检查日期的代码(之前设置的,今天是 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";

现在,我认为在进入循环之前值被更改的原因是当我在“if(($date...){”之前添加一个“print $lastScanned”语句时,打印了分配的任何内容的日期到 $date 之前 - 但是如果我注释掉 '$SCAN{$key}[1] = $date;' 语句,打印语句将打印 '19700101' 日期并且一切正常运行。发生了什么? $SCAN{$key}[1] 除了上面显示的两个地方外,永远不会被触及。

对不起,如果这措辞很糟糕,或者没有意义。我尽力解释了困扰我几个小时的事情。

谢谢!

4

2 回答 2

8

因为您的@data数组是全局的,所以每次执行语句时

$SCAN{$key} = \@data;

您正在分配对同一数组$SCAN{$key}的引用。因此,最终所有值都指向同一个数组,这可能不是您想要的。 @data%SCAN

有几种方法可以解决这个问题。也许最简单的方法是通过将上面@data$SCAN{$key}更改为

$SCAN{$key} = [ @data ];

或者,您可以重写整个循环以使用my在循环内声明的词法数组 - 这样您就可以在每次迭代时创建一个新的单独数组:

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;
}

然而,你真正应该做的,而不是仅仅修复这个特定错误的症状,是了解变量作用域在 Perl 中是如何工作的以及它应该如何使用,并相应地重写你的代码。

特别是,查看您的代码,我非常怀疑您没有在代码中使用编译strict指示。如果你想编写干净的 Perl 代码,你真正应该做的第一件事是在所有脚本前面加上以下两行,紧跟在该#!行之后:

use strict;
use warnings;

strictpragma 迫使您避免某些不良和容易出错的习惯,例如使用符号引用或未声明的全局变量,而pragma使warnings解释器警告您有关其他各种愚蠢、危险、模棱两可或其他不受欢迎的事情(您确实应该处理这些事情)作为错误并修复,直到您不再收到警告)。

当然,这并不意味着您应该在脚本开头使用my(or our) 声明所有变量只是为了strict开心。相反,您应该做的是查看每个变量,查看它实际使用的位置,并在需要它的最内层范围内声明它。(如果您在代码的不同部分重用相同的变量名,请将它们视为单独的变量并分别声明它们。)请记住,您可以在循环语句中声明循环变量,如

foreach my $key (keys %IPS) {

或者

while (my $line = <>) {

附言。我还注意到您向我们展示的代码中有一条令人担忧的评论:

# repeat of the above code, but with ...

一般来说,这种代码重复应该是一个很大的闪光信号,表明你可能做错了什么——编程的黄金法则是“不要重复自己。

当然,在少数非常罕见的情况下,您确实需要以两种不同的方式完成本质上相同的事情,但有如此多的小而随意的差异贯穿其中,因此将整个事情写两次会更清晰。但如果是这种情况,我会感到非常惊讶——我敢打赌,你只能编写该代码一次,然后插入一个

if (not $init and exists ...) {

在合适的位置检查。

于 2012-07-26T22:13:52.063 回答
3

正如 Ilmari 所说,您的问题是 的每个元素都%SCAN指向第一个代码块中的同一个二元素数组,因此所有 IP 地址的相同变量也是如此。@data$SCAN{<anything>}[1]

为了解决这个问题,我的偏好是忘记@data并写

$SCAN{$key} = [ $IPS{$key}, '19700101' ];

每次执行语句时都会生成一个新的匿名数组,并将对它的引用分配为哈希值。

另请注意,我使用了一个字符串作为日期,因为你不能写出类似的东西$date - $lastScanned:日期算术比这更复杂。减去31-JAN-20121-FEB-2012变成20120201 - 20120131或70!

幸运的是,有一些模块可以让这更容易,您可以使用模块Time::Piece,它是一个核心模块(即,它从 Perl v5.9 开始与标准 Perl 一起安装)并且可以让您进行这种算术运算。

在程序的顶部,在use strictand之后use warnings,你写

use Time::Piece;

然后稍后,在你最初的时候,写

my $initial = localtime(0);

进而

my $date = localtime;

您可以通过打印它们来查看这两个值对应的日期

print $initial, "\n";
print $date, "\n";

这将显示类似

Thu Jan  1 00:00:00 1970
Fri Jul 27 01:40:53 2012

一个简单的减法会给你真正的区别,在几秒钟内

print $date - $initial;

所以如果$threshold是几天,你可以通过写来检查间隔

if ( $date - $lastScanned > $threshold * 24 * 60 * 60 ) { ... }

我希望我没有在这里吓到你,但它需要改变,我认为你应该知道。该模块的功能远不止于此,如果您想查看文档,请点击此处。如果您遇到困难,请再问一个问题。

于 2012-07-27T00:46:03.443 回答