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

I have a script that generates an array containing server names. I also have a list of comments ("webserver",
"located in DMZ", etc...) about many of the servers. Then I loop through the array, match up the server name to
the comment and print the information. (The script is more complex, but this is the gist of it.)

Currently, I pass the server name to a subroutine that consists of consecutive if statements:

sub add_comment{ my $x = shift (@_); my $y; $y = "Webserver" if ($x =~ /server1/i); $y = "DMZ Server" if ($x =~ /server2/i); $y = "Oracle Server" if ($x =~ /server3/i); return $y; }

This was an OK method when the server list was small, but now the server list is getting large. I want to
redesign this so I can separate the comment list from the actual script, and to hopefully minimize the number
of if statements. I was thinking of putting all of the comments into another file in the form of SERVER: COMMENT.
Then, while looping through the array, finding the comment associated to the server.

What's a decent way to search the file for the comment? grep()? Open the file into an array or hash?

Replies are listed 'Best First'.
Re: Using a lookup table(?)
by fenLisesi (Priest) on Feb 07, 2007 at 18:03 UTC
    However you store the attribute value pairs, if there are many of them, I would suggest that you load them into a hash. Something like YAML::Syck or Config::Tiny may help with the storage. The code below may give you ideas about the other half of your question:
    use strict; use warnings; my %description_of = ( server1 => "Webserver", server2 => "DMZ Server", server3 => "Oracle Server", ); my $DEFAULT_DESCRIPTION = "Unknown Server"; my @servers = qw(server1 server2 server3 server42); for my $server (@servers) { printf "%s\n", get_description_of( $server ); } exit( 0 ); sub get_description_of { my ($server) = @_; if (defined $description_of{ $server }) { return $description_of{ $server }; } return $DEFAULT_DESCRIPTION; } __END__
    which prints:
    Webserver DMZ Server Oracle Server Unknown Server
    Cheers.
Re: Using a lookup table(?)
by blue_cowdawg (Monsignor) on Feb 07, 2007 at 18:06 UTC
        Open the file into an array or hash?

    Assuming I understand you correctly, and you are using a file of the format:

    server1 Webserver server2 DMZ Server server4 Oracle Server
    you could do the following:
    use strict; use Tie::File; | mumble. | sub get_comments { my $host=shift; my @ry=(); tie @ry,"Tie::File","myfile.txt" or die "myfile.txt: $!"; my @lines = grep $host,@ry; my $line = shift @lines; # should only be one. my @f=split(/[\s\n\t]+/,$line); shift @f; untie @ry; return join(" ",@f); }
    Keep in mind I haven't tested this and this is off the top of my head. There's some error detection logic missing in that code (for instance, nothing for the value $host in the input file) and stuff like that.

    Hope this is a help...


    Peter L. Berghold -- Unix Professional
    Peter -at- Berghold -dot- Net; AOL IM redcowdawg Yahoo IM: blue_cowdawg

      Your ignored the fact that the search was case-insensitive.

      my $host = lc(shift(@_));

      Your grep is wrong.

      my @lines = grep { lc($_) eq $host } @ry;

      Why read the whole file everytime?

      use List::Util qw( first ); my $line = first { lc($_) eq $host } @ry;

      Why use Tie::File if you're not going to take advantage of it?

      use List::Util qw( first ); my $line = first { lc($_) eq $host } <$fh>;

      Why load up the entire file into memory if you look at it one line at a time?

      sub get_comments { my $host = lc(shift(@_)); my $file_name = "myfile.txt"; open(my $fh, '<', $file_name) or die("Unable to open server info file \"$file_name\": $!\n"); while (<$fh>) { return $1 if /^$host:\s*(.*)/; } return; }

      Of course, that's still very expensive if get_comments is called repeatedly. If the list is really that long, some sort of index (or database) would be very useful. A sorted file with fixed length records would also be fine (allowing for a binary search).

            Why load up the entire file into memory if you look at it one line at a time?

        Last I checked... Tie::File does not load the entire file into memory...


        Peter L. Berghold -- Unix Professional
        Peter -at- Berghold -dot- Net; AOL IM redcowdawg Yahoo IM: blue_cowdawg
Re: Using a lookup table(?)
by jettero (Monsignor) on Feb 07, 2007 at 17:55 UTC

    I don't see how you'll get around the list of regulars. You can maybe make it slightly more efficient with something like $y = $1 if $x =~ m/(host1|host2|host3)/i perhaps; but, I think you'd need Benchmark to see if that actually helped.

    If it was my code, and I was getting more than a few dozen lines or so, I'd probably start a table of pre-compiled host regulars or something like so:

    # e.g. $hosts{'DMZ Server'} = qr{(?i:server2)}; while( my ($name, $reg) = each %hosts ) { return $name if $x =~ $reg; }

    Update: I just noticed this little bit, "separate the comment list from the actual script" You can definitely build the hash in a separate file like below, but I think if your list is getting really big, you should look at the packages mentioned in the post below mine. Both are pretty slick.

    use MyHosts; while( my ($name, $reg) = each %MyHosts::hosts ) { } # ... package MyHosts; use strict; our %hosts = ( 'Server Name' => qr(?i:serverpattern), );

    -Paul