Stefan Welscher: Multidimensionale Hashes komplett dereferenzieren?

Moin moin,
Perl macht mich mal wieder mit seinen Referenzen fertig...... HELP!!

Ich habe folgendes Testscript:

#/usr/bin/perl  
  
use strict;  
  
my %I;  
$I{'ce'}{'lan'}{1}{'ifc'}="FastEthernet0/2-3";  
$I{'ce'}{'lan'}{1}{'ip'}="1.1.1.1";  
$I{'ce'}{'lan'}{2}{'ifc'}="GigabitEthernet0/1-2";  
$I{'ce'}{'lan'}{2}{'ip'}="2.2.2.2";  
  
my %old_lan_hash=%{$I{'ce'}{'lan'}};  
my $count_lan=1; my $count_lan_new=1;  
while ($old_lan_hash{$count_lan}{'ifc'})  
{  
	print "\nTesting interface: ".$old_lan_hash{$count_lan}{'ifc'};  
	if ($old_lan_hash{$count_lan}{'ifc'}=~/^(.*)(\d+)-(\d+)/)  
	{  
		my $main=$1; my $start=$2; my $end=$3;  
		for (my $a=$start;$a<=$end;$a++)  
		{  
			%{$I{'ce'}{'lan'}{$count_lan_new}}=%{$old_lan_hash{$count_lan}};  
			$I{'ce'}{'lan'}{$count_lan_new}{'ifc'}="FastEthernet0/".$a;  
			print "\nNew LAN interface: ".$count_lan_new.": ".$I{'ce'}{'lan'}{$count_lan_new}{'ifc'}." (IP:".$I{'ce'}{'lan'}{$count_lan_new}{'ip'}.")";  
			$count_lan_new++;  
		}  
	}  
	$count_lan++;  
}  
print "\n\n";

Aufgabe des Scriptes ist es Interfaceranges zu expanden.
Also wenn ein Nutzer "FastEthernet0/0-1" eingibt, soll das umgewandelt werden in "FastEthernet0/0" und "FastEthernet0/1". Die folgenden Interfaceangaben sollen natürlich nicht überschrieben werden, deswegen speichere ich die bestehenden Interfaces zum parsen in einem neuen Hash %old_hash. Ich möchte jetzt also %old_hash Interface für Interface durchlaufen und dabei das neue LAN-Hash bilden.
Das Problem ist aber, dass das Hash %old_hash nicht komplett dereferenziert ist. D.h. Wenn ich das neue LAN-Interface schreiben will überschreibe ich auch den Wert in %old_hash. Dadurch endet die Schleife dann leider unerwartet. Die Ausgabe sieht dann wie folgt aus:

Testing interface: FastEthernet0/2-3
New LAN interface: 1: FastEthernet0/2 (IP:1.1.1.1)
New LAN interface: 2: FastEthernet0/3 (IP:1.1.1.1)
Testing interface: FastEthernet0/3

Wie kann ich das beheben?
Jeden einzelnen Key zu derefenzieren würde an jeder Stelle an der ich das brauche 50 Zeilen Code verschlingen und das Gesamtscript aufwändiger zum Warten machen, wenn sich mal Parameter dazugesellen (was recht häufig der Fall ist...).

  1. Also wenn ein Nutzer "FastEthernet0/0-1" eingibt, soll das umgewandelt werden in "FastEthernet0/0" und "FastEthernet0/1".

    Also ein Hashreferenzelement, das zuvor einen Scalar als Wert hatte soll neu eine Arrayreferenz beinhalten?
    So etwas sehe ich nicht.
    Statt dessen wurstelst du die Indizes in bereits belebte Hashkeys höherer Ordnung.
    Das ist dein Problem.

    mfg Beat

    --
    ><o(((°>           ><o(((°>
       <°)))o><                     ><o(((°>o
    Der Valigator leibt diese Fische
    1. Also wenn ein Nutzer "FastEthernet0/0-1" eingibt, soll das umgewandelt werden in "FastEthernet0/0" und "FastEthernet0/1".

      Also ein Hashreferenzelement, das zuvor einen Scalar als Wert hatte soll neu eine Arrayreferenz beinhalten?

      Nein, "FastEthernet0/0-1" ist der String, den der User übergibt, damit er bei Switches mit 48 Ports nicht 48 Eingaben machen muss. Nur benötigt das Script selbst dann die realen Portnamen und muss deshalb die dazugehörigen Parameter multiplizieren.

  2. Moin moin,

    Perl macht mich mal wieder mit seinen Referenzen fertig...... HELP!!

    Du machst Dir selbst das Leben schwer...

    my %I;
    $I{'ce'}{'lan'}{1}{'ifc'}="FastEthernet0/2-3";
    $I{'ce'}{'lan'}{1}{'ip'}="1.1.1.1";
    $I{'ce'}{'lan'}{2}{'ifc'}="GigabitEthernet0/1-2";
    $I{'ce'}{'lan'}{2}{'ip'}="2.2.2.2";

    Nenene, so wird dat nüschd. Definiere IF-Objekte, am Besten über eine eigene Klasse und new();

      
    package Interface; # my custom class for Interfaces  
      
    # Constructor  
    sub new{  
     my $class = shift;  
     my $p     = shift; # Parameter  
      
     my $self = {  
       IP => $p->{IP},  
       NAME => $p->{NAME},  
       DEST => $p->{DEST},  
     };  
      
     return bless $self, $class;  
    }  
    package main;  
    my $ifObj = Interface->new({  
     IP => '10.0.0.1',  
     NAME => 'Fa0/0',  
     DEST => 'Kunden-LAN',  
    });  
      
    # usw.  
    
    

    Mehrere Interfaces kannst Su dann zusammenfassen in einem %hash, z.B. alle Interfaces eines Routers oder Switch.

    %router;
    $router{'lan1'} = $ifObj; # das Objekt von weiter oben
    $router{LAN_2} = $nochnIfObj;
    $router{WAN} = $serIfObj;

    -> So gehts bischen besser mit den Referenzen ;-)

    Und dann schauen wir mal weiter...

    Hottü

    1. Nenene, so wird dat nüschd. Definiere IF-Objekte, am Besten über eine eigene Klasse und new();

      Da hast du mich.... mit Oo hab ich noch keinerlei relevante Erfahrung....
      Aber im Prinzip willst du darauf hinaus, dass die Referenzstruktur flacher werden soll? Das mag hier im Beispiel sehr einfach gehen, aber ganz so simple ist der Aufbau dann im Endeffekt nicht. Ein Interface kann z.B. noch beliebig viele Secondary-IPs, IP-Helper-Einträge, oder sogar Kaskaden haben:

      $I{'ce'}{'lan'}{1}{'ifc'}="FastEthernet0/2-3";
      $I{'ce'}{'lan'}{1}{'ip'}="1.1.1.1";
      $I{'ce'}{'lan'}{1}{'sec'}{1}{'ip'}="10.10.10.10";
      $I{'ce'}{'lan'}{1}{'sec'}{2}{'ip'}="20.20.20.20";
      $I{'ce'}{'lan'}{1}{'helper'}{1}="100.100.100.1";
      $I{'ce'}{'lan'}{1}{'helper'}{2}="100.100.100.2";
      $I{'ce'}{'lan'}{1}{'cc'}{1}{'hostname'}="CC_Router1";
      $I{'ce'}{'lan'}{1}{'cc'}{1}{'id'}="11/11/1111-1111/11";
      $I{'ce'}{'lan'}{1}{'cc'}{1}{'ip'}="1.1.1.2";
      $I{'ce'}{'lan'}{2}{'ifc'}="GigabitEthernet0/1-2";
      $I{'ce'}{'lan'}{2}{'ip'}="2.2.2.2";

      Das ganze gibt es dann auch noch mit Subinterfaces:

      $I{'ce'}{'lan'}{1}{'subif'}{1}{'ifc'}="FastEthernet0/2-3";
      $I{'ce'}{'lan'}{1}{'subif'}{1}{'ip'}="1.1.1.1";
      $I{'ce'}{'lan'}{1}{'subif'}{1}{'sec'}{1}{'ip'}="10.10.10.10";
      $I{'ce'}{'lan'}{1}{'subif'}{1}{'helper'}{1}="100.100.100.1";
      ....

      Lässt sich deine Variante dann überhaupt anwenden?
      Ich glaub der Code würde dann im Endeffekt wesentlich komplexer und unübersichtlicher werden, als er aktuell ist. Und Schleifendurchläufe wären mit dem "lan_x"-Key auch nicht mehr möglich, ohne jedes mal einen neuen String zu bauen.
      Auf die schnelle bringt mir das jedenfalls nichts, auch wenn man das evtl. längerfristig ins Auge fassen muss, aber ich sollte bis Montag eine funktionsfähiges neues Release haben. In der Zeit kann ich keine 50.000 Zeilen Code anpassen, bzw. dann eher neu schreiben.....

      1. Nenene, so wird dat nüschd. Definiere IF-Objekte, am Besten über eine eigene Klasse und new();
        Da hast du mich.... mit Oo hab ich noch keinerlei relevante Erfahrung....

        Dann wirds abba Zeit für einen angehenden Netzwerkmanager ;-)

        Aber im Prinzip willst du darauf hinaus, dass die Referenzstruktur flacher werden soll?

        Genauhhh!!!

        Das mag hier im Beispiel sehr einfach gehen, aber ganz so simple ist der Aufbau dann im Endeffekt nicht. Ein Interface kann z.B. noch beliebig viele Secondary-IPs, IP-Helper-Einträge, oder sogar Kaskaden haben:

        Guckst Du:

        Draußen steht ein Switch(1) mit vielen Ports. Den fragst Du über SNMP ab und erstelltst für jeden Port (If) ein Objekt. Jedes IF-Object tust Du in einen Hash. Fürs Management musst Du dann nur noch den Hash durchlaufen:

          
        foreach my $ifObj(keys %switch){  
         $ifObj->{NAME}; # statements go here  
        }  
        
        

        ... und jetzt ist Feierabend. Der nächste Montag kommt bestimmt ;-)

        Hottü

        (1) Oder viele Switches mit noch viel mehr Ports

        1. Guckst Du:

          Draußen steht ein Switch(1) mit vielen Ports. Den fragst Du über SNMP ab und erstelltst für jeden Port (If) ein Objekt. Jedes IF-Object tust Du in einen Hash. Fürs Management musst Du dann nur noch den Hash durchlaufen:

          Na, knapp daneben, um eine Management-Anwendung geht es nicht. Da haben wir schon was gutes (nicht von mir ;) ). Geht um die andere Seite, die Konfigerstellung, und da muss ich mehr oder weniger alles eintüten und loopen können, was der Router so hergibt: PE, CE, NAS, RADIUS, Kaskaden, Plattformkopplungen etc. etc.. Mit Interfaces alleine ist es also wirklich nicht getan.

          Ich hab jetzt aber eine Lösung für das Problem gefunden:
          Wenn ich die Counter einzeln dereferenziere geht es:

          #/usr/bin/perl  
            
          use strict;  
            
          my %I;  
          $I{'ce'}{'lan'}{1}{'ifc'}="FastEthernet0/2-3";  
          $I{'ce'}{'lan'}{1}{'ip'}="1.1.1.1";  
          $I{'ce'}{'lan'}{1}{'sec'}{1}{'ip'}="10.1.1.1";  
          $I{'ce'}{'lan'}{2}{'ifc'}="GigabitEthernet0/1-2";  
          $I{'ce'}{'lan'}{2}{'ip'}="2.2.2.2";  
          $I{'ce'}{'lan'}{2}{'sec'}{1}{'ip'}="20.2.2.2";  
            
          my %old_lan_hash;  
          %{$old_lan_hash{1}}=%{$I{'ce'}{'lan'}{1}};  
          %{$old_lan_hash{2}}=%{$I{'ce'}{'lan'}{2}};  
          my $count_lan=1; my $count_lan_new=1;  
          while ($old_lan_hash{$count_lan}{'ifc'})  
          {  
          	print "\nTesting interface: ".$old_lan_hash{$count_lan}{'ifc'};  
          	if ($old_lan_hash{$count_lan}{'ifc'}=~/^(.*)(\d+)-(\d+)/)  
          	{  
          		my $main=$1; my $start=$2; my $end=$3;  
          		for (my $a=$start;$a<=$end;$a++)  
          		{  
          			%{$I{'ce'}{'lan'}{$count_lan_new}}=%{$old_lan_hash{$count_lan}};  
          			$I{'ce'}{'lan'}{$count_lan_new}{'ifc'}= $main.$a;  
          			print "\nNew LAN interface: ".$count_lan_new.": ".$I{'ce'}{'lan'}{$count_lan_new}{'ifc'}." (IP:".$I{'ce'}{'lan'}{$count_lan_new}{'ip'}."/SEC:".$I{'ce'}{'lan'}{$count_lan_new}{'sec'}{1}{'ip'}.")";  
          			$count_lan_new++;  
          		}  
          	}  
          	$count_lan++;  
          }  
          print "\n\n";
          

          Da musste ich jetzt zwar eine kleine Schleife vorschalten, ist aber immernoch viel besser, als jeden Wert einzeln zu mappen!

          (insgesamt trotzdem nicht schön, ich weiß, aber ich bekomm meinen Code evtl. bis Montag fertig um ein Kundenprojekt nicht zu gefährden)