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

I'm continuing on a script on which the Monks here have helped this week. I've come up against another problem that I, as a Perl pre-novice, can't quite figure out. Up to this point, the script does what I want it to. A synopsis:

The script opens up the config of a PIX firewall and pulls out all of our IP addresses to a file, pix.out. Pix.out is then ran through the shell command 'sort -u' to eliminate all duplicate addresses, creating ns.in. Ns.in is then processed through the Nslookup.pm module to resolve all IP addresses to FQDNs. The output of the nslookup routine is pushed to a hash, %hosts with $ip as the key and $a, holding th FQDN, as the value. Now, I want to use the key and value in the hash to search all the IPs out of the PIX config and replace all of them with the appropriate FQDN. Another point about the PIX config- it will sometimes hold two different IP addresses per line. So, here is what I've tried now: FQDN is the output file.
open INFILE, "longwood.pix"; open FQDN, ">fqdn.pix" or die "Can't open der finalen filen: $!"; @final = <INFILE>; close INFILE; while ( ($ip, $a) = each (%hosts)) { foreach $line (@final) { 's/$ip/$a/g'; print FQDN @final; } } close INFILE; close FQDN;
The results of this have been nothing. If I put parentheses aroung the variables in the s///g regex, I end up with an ever growing file. The end result we want to have is a copy of the orig PIX config with all the IPs substituted with FQDNs. Thanks, Monger

Replies are listed 'Best First'.
Re: Search and replace in an array using data in hash
by Abigail-II (Bishop) on Aug 08, 2003 at 15:10 UTC
    Uhm, you have quotes around the substitution. Aren't you using warnings? Perl would have warned.

    BTW, I would have used different logic. You don't have to loop over the hash, just loop over the lines in the file, and do something like:

    use Regexp::Common; s/($RE{net}{IPv4})/$hash{$1}/g;

    Abigail

      In case the hash doesn't contain the entire possible universe of domain names, you may want to offer a fall back.
      use Regexp::Common; s/ ( $RE{net}{IPv4} ) / $hash{$1} || $1 /xge;

      --
      [ e d @ h a l l e y . c c ]

        No, you don't want to do that. That makes all your substitutions slower, and it's not necessary. Remember that the files were already scanned for IP numbers, so no unknown IP numbers should be present in the file. If some IP numbers don't resolve, just put a value in the hash that's the same as the key.

        Abigail

Re: Search and replace in an array using data in hash
by Zaxo (Archbishop) on Aug 08, 2003 at 15:32 UTC

    You're not using the fast lookup properties of a hash. Let $ipre be the regular expression you use for recognising ip addresses when you build %hosts. Make sure it captures the ip in $1. Reading INFILE line by line, and writing FQDN as we go,

    { local( *INFILE, *FQDN, $_); open INFILE, '< /path/to/longwood.pix' or die $!; open FQDN, '> /path/to/fqdn.pix' or die $!; while (<INFILE>) { s/$ipre/(exists($hosts{$1})?$hosts{$1}:$1)/eg; print FQDN $_ or die $!; } close FQDN or die $!; close INFILE or die $!; }

    After Compline,
    Zaxo

      After looking at your suggestion, I'm not sure if it would work. So if you could, please explain. My questions follow.

      What I wanted to do was search on %hosts{$ip} and replace with %hosts{$a} where IP is the IP address of the host and $a is the hostname given by nslookup. I couldn't tell if that is exactly what your s///eg process was doing. Thanks, Monger
        Well, yes, there was viel magisches in that one line. Let's pull it apart.

        Because you said there could be more than one IP address per line Abigail-II, halley and Zaxo all understood they needed to find just IP addresses, maybe more than one, and then _maybe_ substitute a name. So they said:

        s/ <IP address pattern> / <magic> / g
        using the 'g' to match as many times as needed and using a special pattern to match only things that looked like IP addresses.

        For the special pattern Zaxo just said '$ipre' because there are a lot of REs you could use to match IP addresses. Abigail-II suggested you use the Regexp::Common module which has a predefined RE for that.

        So s///g will find each IP address in a line, save that matched string in $1, and then do the second part, the substitution. But we want to lookup the string and replace it only if it is _found_ in your hash.

        If you use the 'e' modifier on a s/// you are saying the second substitute part has _code_ in it. You can make decisions about exactly what to substitute. Zaxo used

        ( exists($hosts{$1}) ? $hosts{$1} : $1 )
        to make the decision very clear. He said that if the matched IP address (saved in $1) could be found in your hash (something existed in %hosts with the key $1) then we want to substitute the value in $hosts{$1}, otherwise we just want to use the original matched string (we don't have anything to substitute).

        What halley used was a shortcut way of saying the same thing:

        $hash{$1} || $1
        The || 'or' operator will use the first value if it is not false otherwise it will use the second value. If you try to lookup a value using a key when you haven't stored something in the hash using that key, Perl doesn't die but rather quietly says "I don't have anything here for that key." The value returned by $hash{'unbekannt'} is simply the 'undef' value. 'undef' is false so the above expression will throw away the first value and use the second value. So if the IP address is found in the hash we use '$hash{$1}' otherwise we use '$1'.

        Your code suggests that the keys of %hosts are ip numbers, and the values are a cache of the corresponding hostnames. That is the structure I wrote to. If %hosts is something different, please describe it and show the code which populates it.

        After Compline,
        Zaxo