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;' ステートメントをコメントアウトすると、print ステートメントは '19700101' 日付を出力し、すべてが正常に機能します。 $SCAN{$key}[1] は、上記の 2 か所を除いて、まったく触れられていません。

これが非常に悪い言い回しであったり、意味をなさない場合は申し訳ありません。私は、何時間も頭を悩ませてきた何かを説明するために最善を尽くしました.

ありがとうございました!

4

2 に答える 2

8

配列はグローバルであるため@data、ステートメントを実行するたびに

$SCAN{$key} = \@data;

同じ配列$SCAN{$key}への参照に割り当てています。したがって、すべての値が最終的に同じ配列を指すことになりますが、これはおそらくあなたが望むものではありません。 @data%SCAN

それを修正する方法はいくつかあります。おそらく最も簡単なのは、上記の行を に変更して、コードに配列のコピーへの参照を割り当てることです。@data$SCAN{$key}

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

別の方法として、ループ内で with 宣言されたレキシカル配列を使用するようにループ全体を書き直すこともできます。これ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 コードを書きたい場合、最初#!にすべきことは、次の 2 行をすべてのスクリプトの行の直後に追加することです。

use strict;
use warnings;

プラグマはstrict、シンボリック参照や宣言されていないグローバル変数を使用するなど、特定の悪い習慣やエラーを起こしやすい習慣を避けるように強制しますがwarningsプラグマは、インタープリターに、他のさまざまなばかげた、危険な、あいまいな、またはその他の望ましくないことについて警告させます (これらは実際に処理する必要があります)。エラーとして表示され、警告が表示されなくなるまで修正します)。

もちろん、これは、スクリプトの先頭ですべての変数をmy(またはour)で宣言するだけでよいという意味ではありませんstrict。代わりに、各変数を見て、実際に使用されている場所を確認し、それが必要な最も内側のスコープで宣言する必要があります。次のように、ループ文でループ変数を宣言できることに注意してください。

foreach my $key (keys %IPS) {

また

while (my $line = <>) {

Ps。また、あなたが示したコードに気になるコメントがあることに気付きました。

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

一般に、この種のコードの重複は、おそらく何か間違ったことをしているという大きな点滅信号になるはずです。プログラミングの黄金律は、「同じことを繰り返さない」ことです。

もちろん、本質的に同じことを 2 つの異なる方法で行う必要がある非常にまれなケースはほとんどありませんが、非常に多くの小さな恣意的な違いが全体に散りばめられているため、全体を 2 回書いた方がきれいです。しかし、ここでそうだったとしたら、私は非常に驚かれることでしょう。そのコードを 1 回だけ記述して、おそらく

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

適当なところで確認。

于 2012-07-26T22:13:52.063 に答える
3

Ilmari が言うように、問題は、 のすべての要素が、最初のコード ブロックにあった同じ2 要素配列を%SCAN指しているため、すべての IP アドレスの変数が同じであることです。@data$SCAN{<anything>}[1]

これを修正するために、私の好みは忘れて@data書くことです

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

これは、ステートメントが実行されるたびに新しい無名配列を生成し、ハッシュの値として参照を割り当てます。

次のようなものを書くことはできないため、日付に文字列を使用したことにも注意してください$date - $lastScanned。日付演算はそれよりも複雑です。31-JAN-2012から引くと、または 701-FEB-2012になります。20120201 - 20120131

幸いなことに、これを簡単にするモジュールがあり、モジュールを使用できますTime::Piece。これはコア モジュール (つまり、Perl v5.9 以降の標準 Perl と共にインストールされます) であり、この種の演算を実行できます。

プログラムの先頭の と の後use strictuse warnings、次のように記述します。

use Time::Piece;

そして後で、最初に、次のように書きます

my $initial = localtime(0);

その後

my $date = localtime;

2つの値が対応する日付を印刷するだけで確認できます

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 に答える