【问题标题】:Perl changing value within a conditional before entering the conditional?Perl在输入条件之前更改条件内的值?
【发布时间】:2012-07-25 14:13:24
【问题描述】:

我正在编写一个 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)。在上面的代码和下面的代码之间,除了 cmets 什么都没有

    $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] 除了上面显示的两个地方外,从未被触摸过。

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

谢谢!

【问题讨论】:

    标签: arrays perl hashmap conditional-statements


    【解决方案1】:

    因为您的 @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;
    }
    

    但是,您真正应该做的是学习how variable scoping works in Perl 以及应该如何使用它,并相应地重写您的代码,而不是仅仅修复这个特定错误的症状。

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

    use strict;
    use warnings;
    

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

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

    foreach my $key (keys %IPS) {
    

    while (my $line = <>) {
    

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

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

    一般来说,这种代码重复应该是一个很大的闪烁信号,表明你可能做错了什么——编程的黄金法则是“Don't repeat yourself.

    当然,在极少数情况下,您确实需要以两种不同的方式做本质上相同的事情,但有这么多小而任意差异贯穿始终,将整个内容写两次更干净。但如果是这种情况,我会感到非常惊讶——我敢打赌,你只能编写一次该代码,并且可能只插入一个

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

    在合适的位置检查。

    【讨论】:

    • 谢谢!这很有帮助!我将阅读您链接的所有内容并修改令人担忧的代码部分!
    【解决方案2】:

    正如 Ilmari 所说,您的问题是 %SCAN 的每个元素都指向 same 二元素数组,即第一个代码块中的 @data,所以 $SCAN{&lt;anything&gt;}[1] 是相同的所有 IP 地址的变量。

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

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

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

    还要注意,我使用了一个字符串作为日期,因为你不能写出像$date - $lastScanned 这样的东西:日期算术比这更复杂。从1-FEB-2012 中减去31-JAN-2012 将变为20120201 - 20120131 或70!

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

    在程序的顶部,在use strictuse 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 ) { ... }
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-11
      • 2012-09-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多