rspence has asked for the wisdom of the Perl Monks concerning the following question:

EDITOR's NOTE: the following node has been updated from it's original wording. You can view the orginal question here: http://perlmonks.thepen.com/303087_orig.html

Good Evening Reveared monks,

My problem was not in my nested loop - although as clunky (senseless, inefficient you say) I still think it would work - but before I even get to it. As I suspected since I close both while loops that create the 2 hashes *before* I get to the loop perl gives it only the end of each hash to play with. I don't know this for a fact, but, upon leaving the first while open then opening the second fh and second while loop, thus having "everything in memory" the loop works. (I've left the commented lines leaving each while loop open) I have included below the first iteration of the script that works using sauoq's latest loop suggestion, with a slight edit. Now on to the final snmp hash and merging it with this one.

My apologies for misusing any monk's cycles. I asked for a solution to a problem that was misdiagnosed. Alas the socratic method....

#!/usr/bin/perl -w # # # mac-bp-ifIndex.pl - a test script to gather the 1.3.6.1.2.1.17.4.3.1 +.2 OID # and the 1.3.6.1.2.1.17.1.4.1.2 OID information f +rom switches # (via snmp polls) then merge the tuples to create + a single # MAC -> ifIndex pairs list. # # # + use strict; + my $depot_dir="/nms/depot"; my $comm_string="xxx"; my $switch_list="/nms/switch_fetch/switch_list"; my $switch; my $mac; my $bp; my $bp2; my $ifIndex; my $snmpwalk; + my (%mactobp, %bptoifIndex); + open (SWITCH, "$switch_list") || die "Can't open $switch_list file"; open (LOG, ">$depot_dir/mac-bp-ifIndex.txt") || die "Can't open $depot +_dir/mac-bp-ifIndex.txt file"; + while (<SWITCH>) { chomp($switch="$_"); $snmpwalk="/usr/local/bin/snmpwalk -c $comm_string $switch "; + open(MACtoBP, "$snmpwalk .1.3.6.1.2.1.17.4.3.1.2 |") || die "c +an't do .1.3.6.1.2.1.17.4.3.1.2: $!\n"; + while (<MACtoBP>) { /SNMPv2-SMI::mib-2.17.4.3.1.2.(\d+)\.(\d+)\.(\d+)\.(\d ++)\.(\d+)\.(\d+)\s+\=\s+\w+:\s(\d+)$/gm; + $mac=sprintf ("%lx:%lx:%lx:%lx:%lx:%lx",$1, $2, $3, $4 +, $5, $6); $bp=$7; + %mactobp = ( $mac => "$bp" ); + # } + + open(BPtoifIndex, "$snmpwalk .1.3.6.1.2.1.17.1.4.1.2 |") || di +e "can't do .1.3.6.1.2.1.17.1.4.1.2: $!\n"; + while (<BPtoifIndex>) { /SNMPv2-SMI::mib-2.17.1.4.1.2.(\d+)\s+\=\s+\w+:\s(\d+) +$/gm; + $bp2=sprintf ("%d",$1); $ifIndex=sprintf ("%d",$2); + %bptoifIndex = ( $bp2 => "$ifIndex" ); + # } + + for my $mac (keys %mactobp) { if (exists $bptoifIndex{$bp} ) { $mactobp{$mac} = $bptoifIndex{$ifIndex}; print "$mac -> $ifIndex\n"; } } } } + } + close MACtoBP; close BPtoifIndex; close LOG; close SWITCH;

rspence

edited: Mon Nov 3 13:48:02 2003 by jeffa - added editor's note, readmore, formatting

Replies are listed 'Best First'.
Re: Merging 2 hashes
by Abigail-II (Bishop) on Oct 29, 2003 at 20:07 UTC
    I think you want something like this:
    while (my ($mac, $bp) = each %mactobp) { $mactobp {$mac} = $bptoifIndex {$bp} if exists $bptoifIndex {$bp} }

    Iterating over an hash just to find a certain index (what you are doing in the inner loop) is just silly. The reason to have hashes is to be able to quickly find keys, to avoid doing a linear search.

    Abigail

      Unfortunately this code yields the same result as my original nested loop - it looks only at the last tuple in each hash. I am beginning to think the problem is above, once I am no longer in either of the while loops that create each hash?? Still trying other offerings....
Re: Merging 2 hashes
by sauoq (Abbot) on Oct 29, 2003 at 20:17 UTC

    Are you just trying to do this?

    $mactobp{ $_ } = $bptoifIndex{ $mactobp{ $_ } } for keys %mactobp;

    Or, maybe a bit more robustly:

    for my $key (keys %mactobp) { if (exists $bptoifIndex{ $mactobp{ $key } }) { $mactobp{ $key } = $bptoifIndex{ $mactobp{ $key } }; } }

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Merging 2 hashes
by jonadab (Parson) on Oct 29, 2003 at 20:14 UTC

    Besides the problems with each that others have mentioned, you're doing something very inefficient here:

    while (($bp2, $ifIndex) = each (%bptoifIndex)) { print "$bp2\n"; # testing tool if ($bp eq "$bp2") { $mactobp{$mac} = $ifIndex; print "$mac matches with $ifIndex.\n"; }

    All this does is check whether there's a key in %bptoifIndex that matches $bp (and if so assign the associated value to $mactobp{$mac}), but it does this by looping through all the keys in the whole hash and testing each. However, a large part of the benefit of using hashes is that hash lookups are quick and easy, so you don't have to do this sort of schenaneghans. You can rewrite the above while loop as a simple conditional, like this:

    if (exists $bptoifIndex{$bp}) { $mactobp{$mac} = $bptoifIndex{$bp}; print "$mac matches with $bptoifIndex{$bp}.\n" if $debug; } else { warn "$mac / $bp does not have an entry in %bptoifIndex\n"; }

    This is not only faster, it's also a whole lot easier to read, IMO, and therefore will be easier for you to maintain down the road. Also, for the print statements that are just there for testing purposes, I usually like to suffix them all with if $debug so that when I'm done debugging I can turn them all off by commenting out one line at the top of the file ($debug=1;).

    update: On second thought, you might be able to completely do away with this whole chunk of code, just by changing subsequent code from $mactobp{$mac} to $bptoifIndex{$mactobp{$mac}}, especially if you're pretty sure every mac has an ifIndex in the second hash, or if it's acceptable to get undef for ones that don't.


    $;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}} split//,".rekcah lreP rehtona tsuJ";$\=$ ;->();print$/
Re: Merging 2 hashes
by etcshadow (Priest) on Oct 29, 2003 at 19:51 UTC
    You don't want to loop on while (... each %hash) { and modify the hash inside of the loop. Try doing:
    foreach my $key (keys %hash) { my $val = $hash{$key}; ... }
    instead. That way you are iterating over what were the items in the hash before you began iterating.

    Oh, and you should really format posts before putting 'em up. Use the "Preview" button; it's your friend. Updated: thanks!


    ------------
    :Wq
    Not an editor command: Wq
Re: Merging 2 hashes
by Limbic~Region (Chancellor) on Oct 29, 2003 at 20:01 UTC
    rspence,
    According to perldoc -f each, you should not add keys to a hash while you are iterating over it in an each loop.

    It is also likely that you can get away with just one loop by using exists and/or defined. Since I couldn't figure out exactly what you wanted, I am providing the nested loop framework and leaving the reading of perldoc up to you to see if it can be shortened.

    #!/usr/bin/perl -w use strict; my (%hash1, %hash2); # Defined elsewhere in the program for my $key_h1 (keys %hash1) { my $val_h1 = $hash1{$key_h1}; for my $key_h2 (keys %hash2) { my $val_h2 = $hash2{$key_h2} # You can now check $key_h1/2 & $val_h1/2 # And safely modify %hash1 or %hash2 } }
    Cheers - L~R

    Update: Abigaill is correct (as usual), but I was confused as to what rspence actually wanted.

    Since the root node has been updated with clarifications, my post looks bad

      According to perldoc -f each, you should not add keys to a hash while you are iterating over it in an each loop.

      That's true, but the OP wasn't adding keys to the hash. He was modifying values. And that's perfectly save as the iterator iterates over keys, not values.

      Abigail

        That's true, but the OP wasn't adding keys to the hash.

        And this brings up the question, why was the OP's code failing? Iterating over the inner hash is senseless, but it should work.

        -sauoq
        "My two cents aren't worth a dime.";
        
Re: Merging 2 hashes - updated again
by Hena (Friar) on Oct 30, 2003 at 08:19 UTC
    I'm curious, since i haven' done anything like this with switches, i know a little and thus a few simple questions.

    Are you trying to create two hashes and see how much of one matches the other? Meaning is there a reason why one while is inside the other one?
    Thing that is strange to me also is that you seem to replace entire hash in both hashes within each while loops pass (although this might come from my limited understand of how to pass values into hash) or is this intentional?
      Hi Hena, First things first - my goal is to build a list of MAC_addr->switch_port pairings in "real-time". One can do this with managed switches. Unfortunately I am dealing with layer 2 switches and due to the environment I work in certain Cisco functions must be turned off. The only way to reach my goal, that I've found (even on Cisco's site) is to combine the output of 3 different OIDs. (Assuming you know what an OID is...). Anyway, generally speaking, I end up with 3 hashes: A->B B->C C->D. Thus my post - my end goal is A->D. Also: the following is true; for every A ther is a C, and for every B there is a D, so my perl task is to iterate over every A, match its B with the corresponding C in the second hash, then match that C (key) with its corresponding D (value) in the third hash. (FYI A=MAC_addr, B=bridge port ID, C=ifIndex (Interface Index #) and D=switch_port#). I hope that clarifies at least my goal. To answer your questions - switching the values while inside the while loops is the only way I found to match A->C. I found that when I close the 2 loops, in order, the "matching" logic at the end only sees the last key->value pair in each hash. This is why I wondered if I should be utilizing references instead of playing with the "real" values in memory. (Or I should go ahead and try my original nested loop approach that the monks have officially frowned upon. :) ) Anyway - being my usual verbose self. Thanks, rspence