http://qs321.pair.com?node_id=391205

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

I'm in the process of writing a Perl script which will set up the relevant Oracle Environmental Variables for any of our local databases. I created hash tables for each of these instances and then hit a snag. Instead of duplicating the code for each hash table I thought I would just have the one piece of code with the relevant hash table name as a variable. I can find no way to make this work. Is this possible & if so how? I've searched the various Perl books I have and can find no reference to this, can anyone help? The code supplied is not complete I was just ensuring that a variable name for a hash table would work when I hit this problem.
#!/usr/bin/perl -w # # ############################################################### # Extra Modules should be declared here. # # All these modules MUST be found on the server attempting to # # call this script. # ############################################################### # use strict ; # Enforce private + variables use lib '/home/rcruickshank/Perl/Perl_Modules' ; # Where ACC_Vario +us lives use Cwd ; # Current Working + Directory Module use Mail::Sender; # Email module # ############################################################### # ACC_Various is an in-house written Perl module and MUST be # # found in the current path if this script is to work. The # # use lib 'xxxxxxxxxx' command should be used to point to the # # path that this module can be found on if this is not the # # case. (Obviously this code MUST preceed the use ACC_Various # # call!) # ############################################################### # use ACC_Various qw(end_it mail mail_log update_report) ; # ############################################################### # HASH TABLES ############################################################### my %ISWTEST = ( "TERM" => "vt220", "EDITOR" => "vi", "SECTRAN_DIR" => "/home/interface/sectran", "ORACLE_SID" => "ISWTEST" , "ORACLE_HOME" => "/isw/tpp/oracle/test/8.1.7" , "LD_LIBRARY_PATH" => "/isw/tpp/oracle/test/8.1.7/lib:/usr/lib/:/usr/ +ucblib" , ) ; # my %ISWTEST_PATH = ( "TEST1" => "/usr/local/bin:", "TEST2" => "/usr/bin:", "TEST3" => "/usr/ucb:" , "TEST4" => "/etc:" , "TEST5" => "/isw/tpp/oracle/test/1.6.1/idst/bin:" , "TEST6" => "/isw/tpp/oracle/test/1.6.1/idst/forms60/mesg:", "TEST7" => "/isw/appwork/test/finance/guifmxs:" , "TEST8" => ".:" , "TEST9" => "/isw/tpp/oracle/test/8.1.7/bin:" , "TEST10" => "/isw/tpp/oracle/test/8.1.7/dbs", ) ; # my %ISWLIVE = ( "TERM" => "vt220", "EDITOR" => "vi", "SECTRAN_DIR" => "/home/interface/sectran", "ORACLE_SID" => "ISWLIVE" , "ORACLE_HOME" => "/isw/tpp/oracle/live/8.1.7" , "LD_LIBRARY_PATH" => "/isw/tpp/oracle/live/8.1.7/lib:/usr/lib/:/usr/ +ucblib" , ) ; # my %ISWLIVE_PATH = ( "LIVE1" => "/usr/local/bin:", "LIVE2" => "/usr/bin:", "LIVE3" => "/usr/ucb:" , "LIVE4" => "/etc:" , "LIVE5" => "/isw/tpp/oracle/live/1.6.1/ids/bin:" , "LIVE6" => "/isw/tpp/oracle/live/1.6.1/ids/forms60/mesg:", "LIVE7" => "/isw/appwork/live/finance/guifmxs:" , "LIVE8" => ".:" , "LIVE9" => "/isw/tpp/oracle/live/8.1.7/bin:" , "LIVE10" => "/isw/tpp/oracle/live/8.1.7/dbs", ) ; # my %ISWTEACH = ( "TERM" => "vt220", "EDITOR" => "vi", "SECTRAN_DIR" => "/home/interface/sectran", "ORACLE_SID" => "ISWTEACH" , "ORACLE_HOME" => "/isw/tpp/oracle/general/8.1.7" , "LD_LIBRARY_PATH" => "/isw/tpp/oracle/general/8.1.7/lib:/usr/lib/:/u +sr/ucblib" , ) ; # my %ISWTEACH_PATH = ( "LIVE1" => "/usr/local/bin:", "LIVE2" => "/usr/bin:", "LIVE3" => "/usr/ucb:" , "LIVE4" => "/etc:" , "LIVE5" => "/isw/tpp/oracle/general/1.6.1/ids/bin:" , "LIVE6" => "/isw/tpp/oracle/general/1.6.1/ids/forms60/mesg:", "LIVE7" => "/isw/appwork/general/finance/guifmxs:" , "LIVE8" => ".:" , "LIVE9" => "/isw/tpp/oracle/general/8.1.7/bin:" , "LIVE10" => "/isw/tpp/oracle/general/8.1.7/dbs" ) ; # ############################################################### # Set up variables # ############################################################### # Date and time variables for headings in Emails/files etc. # ############################################################### # my ($min, $hour, $day, $mon, $year) = (localtime) [1,2,3,4,5] ; # my $date = sprintf("%02d/%02d/%04d", $day, $mon +=1, $year += + 1900); my $ren_date = sprintf("%04d%02d%02d", $year, $mon, $day) ; my $extract_date = sprintf("%04d%02d%02d" ,$year, $mon, $day) ; my $ptime = "$hour:$min" ; my $filedate = undef ; my $mday = undef ; my $mtime = undef ; my $fdate = undef ; # ################################ # Parameters # ################################ # my $DB_name = undef ; # ##################### # Current timestamp # ##################### # my $now = time ; # ################## # Cut-off dates # ################## # my $cdate = undef ; my $cday = undef ; my $cmon = undef ; my $cyear = undef ; # ################## # Start date # ################## # my $sdate = undef ; my $sday = undef ; my $smon = undef ; my $syear = undef ; my $p_sdate = undef ; # ############################################################### # Files & Directories # ############################################################### # my $logfile = 'ACC_ORACLE_ENV.log' ; # ############################################################### # mail_log variables. # ############################################################### # my $script = 'ACC_ORACLE_ENV' ; my $domain = 'mailhost' ; my $from = 'The Wonderful World Of Perl' ; my $subject = "URGENT - $script Failed!" ; my $msg = "This should change to reflect what is happening." ; my $attachments = undef ; # ###################################### # Email recipient lists # ###################################### # my $to = 'ronniec@it.aberdeen.net.uk' ; # ######################################## # Messages used by SUB end_it # ######################################## # my $null = "none" ; my $text = "Unable to write the following to $logfile :-\n" ; my $topline = "The error message is -\n" ; # ############################################################## # BOOLEAN VARIABLES # ############################################################## # my $mail_msg = 1 ; my $PARAMETER_ERROR = 0 ; # ############################################################### # COMMON OR GARDEN SCALAR VARIABLES # ############################################################### # my $param_no = 0 ; my $key = undef ; my $value = undef ; # ############################################################### # PROCESSING # ############################################################### # print "\n\t\t<***** SOR $script *****>\n" ; # ############################################################### # Check for Parameters # ############################################################### # $param_no = scalar @ARGV ; # if ($param_no == 1) { # One parameter has been supplied # $DB_name = $ARGV[0] ; if ($DB_name =~ /[ISWLIVE ISWTEST ISWTEACH]/) { print "\n\tProcessing $DB_name Environment.\n\n" ; } else { print "\n\tThink again muppett!\n" ; $msg = "$DB_name is not a database known to this script!" ; &end_it($mail_msg,$to,$from,$subject,$msg,$topline) ; } # } else { print "\n\t<***** There MUST be a parameter supplied! *****>" ; print "\n\n\t<***** $script Failed to run. *****>\n\n" ; $msg = "DB name parameter not supplied" ; &end_it($mail_msg,$to,$from,$subject,$msg,$topline) ; } # while (($key,$value) = each %$DB_name) { print "\tKey :: $key\n" ; print "\tValue :: $value\n" ; } # print "\n\t\t<***** EOR $script *****>\n" ;

This results in the following error -
<***** SOR ACC_ORACLE_ENV *****>
Processing ISWTEST Environment.
Can't use string ("ISWTEST") as a HASH ref while "strict refs" in use at xxrc_ne w_profile.pl line 224.
Removing the "use Strict" allows the script to run to completion but none of the key/value pairs are printed so I don't think it is actually working. Cheers, Ronnie

Replies are listed 'Best First'.
Re: hash name as a variable?
by ikegami (Patriarch) on Sep 15, 2004 at 16:53 UTC

    First, something quick and unrelated. In regexps, [] is for single character choices. [ab de] means 'a' or 'b' or 'c' or 'd' or a space. Use vertical bars to seperate longer choices instead of using square brackets:
    if ($DB_name =~ /ISWLIVE|ISWTEST|ISWTEACH/) {

    Back to the question at hand. That error is... not an error per say; it's a self-inflicted limitation. Under use strict 'refs' (included in use strict), $varname = 'foo'; %$varname; gives an error. The idea behind use strict is to force you to declare your variables to avoid typos. There's no way to check that using the above syntax, so it's disallowed. The quick way to solve it is to temporarily and locally disable strict refs:

    my $db_hash = do { no strict 'refs'; \%$DB_name }; while (($key,$value) = each %$db_hash) { print "\tKey :: $key\n" ; print "\tValue :: $value\n" ; }

    On to why it's printing nothing. %$varname looks for global variables, not lexicals. (Lexicals are my variables). Lexicals don't have a name at runtime, so they can't be looked up in this or any other fashion. So you're grabbing the global %ISWTEST, which doesn't exist. Isn't that exactly what use strict was trying to protect you from doing? Change 'my' to 'our' for your hashes to fix this problem.

    All that being said, I recommend against those changes. I'd also avoid the eval "" someone suggested for being unecessarily costly. There's a nice easy way of fixing your problems that's strict-friendly:

    ... my %DBHashLookup = ( ISWLIVE => [ \%ISWLIVE, \%ISWLIVE_PATH ], ISWTEST => [ \%ISWTEST, \%ISWTEST_PATH ], ISWTEACH => [ \%ISWTEACH, \%ISWTEACH_PATH ], ); ... $DB_name = $ARGV[0]; $DB_info = $DBHashLookup{$DB_name}; if (defined($DB_info)) { ($DB_hash, $Path_hash) = @$DB_info; print "\n\tProcessing $DB_name Environment.\n\n" ; ... while (($key,$value) = each %$DB_hash) { ...

    Simple elegance. Sweet, eh?

    While I still have the soapbox *wink*, may I recommend a stylistic change that will make your code easier to read? Instead of if (!error) { do something } else { end it }, why not use if (error) { end it } do something. Here's what your code would look like:

    $param_no = scalar @ARGV ; # if ($param_no != 1) # Other than one parameter has been supplied { print "\n\t<***** There MUST be a parameter supplied! *****>" ; print "\n\n\t<***** $script Failed to run. *****>\n\n" ; $msg = "DB name parameter not supplied" ; &end_it($mail_msg,$to,$from,$subject,$msg,$topline) ; } # $DB_name = $ARGV[0]; $DB_info = $DBHashLookup{$DB_name}; # if (!defined($DB_hash)) { print "\n\tThink again muppett!\n" ; $msg = "$DB_name is not a database known to this script!" ; &end_it($mail_msg,$to,$from,$subject,$msg,$topline) ; } # print "\n\tProcessing $DB_name Environment.\n\n" ; # ($DB_hash, $Path_hash) = @$DB_info; while (($key,$value) = each %$DB_hash) { print "\tKey :: $key\n" ; print "\tValue :: $value\n" ; } #

    See how nicely it flows?

      Though I don't actually send any, you would most definately have made my Christmas card list for your help. I've implemented the code including your "stylistic soapbox wink" and all is now working perfectly. There was one typo that threw me for a minute - if (!defined($DB_hash)) should be $DB_info and one piece of syntax that I'd not seen before. The @$ in ($DB_hash, $Path_hash) = @$DB_info; is puzzling me I'm guessing this has something to do with the referencing in the %DBhashlookup table?
      Sorry to be a pest but I THINK I've worked it out now. The scalar values in the DBhashlookup table are actually anonymous arrays which are later dereferenced via the @$ in the
      ($DB_hash, $Path_hash_ = @$DB_info line?
      Or am I still way off the mark? Cheers,
      Ronnie "must start using references more" Cruickshank
        Bingo! That's exactly it.
Re: hash name as a variable?
by jeremyh (Beadle) on Sep 15, 2004 at 16:07 UTC
    Ronnie, I would set it up in a single hash like this:
    my ($db, $key); my %DBs = ( "ISWLIVE" => { "LIVE1" => "/usr/local/bin", "LIVE2" => "whatever" }, "Next DB" => { "LIVE1" => "/usr/local/bin", "LIVE2" => "whatever" } ); # etc. for all your DB's and key/values

    Then you can iterate through the keys of %DBs like this:
    foreach $db ( sort keys %DBs ) { foreach $key ( sort keys %{ $DBs{$db} } ) { printf "DB = %s, key = %s, value = %s\n", $db, $key, $DBs{$db}->{$key}; # do whatever processing you want } }
Re: hash name as a variable?
by Roger (Parson) on Sep 15, 2004 at 15:22 UTC
    Your regex test
    $DB_name =~ /[ISWLIVE ISWTEST ISWTEACH]/

    should really be
    $DB_name =~ /(ISWLIVE|ISWTEST|ISWTEACH)/

    And then later...
    each %$DB_name
    I would create a reference to hash with:
    $HREF = eval "\\\%$DB_name"; ... if ($HREF->{key} ...)
         $HREF = eval "\\\%$DB_name";

      Ouch. Please don't do that! And if you do, please check $@ afterwards since eval will trap fatal errors silently.

      -sam

        Thanks - I think you just helped me solve a different problem.
        Checking $@ showed where my script was bombing.
Re: hash name as a variable?
by samtregar (Abbot) on Sep 15, 2004 at 20:52 UTC
    Ronnie, you've stumbled onto one of the darker corners of Perl, the difference between lexical (my) variables and package/global (our, local) variables. The critical expression here is:

    while (($key,$value) = each %$DB_name) {

    This code attempts to use $DB_name as a symbolic-reference to find the hash to be indexed. Strict-mode forbids symbolic-references because they don't work with lexical (my) variables. When you turn off strict you get the package variable %IWSTEST (or whatever is in $DB_name) which doesn't exist since you declared your hashes with my().

    Ok, so now you know what the problem is, how should you solve it? Here are two options:

    • Declare your variables with our() or use vars which will make them package variables accessible by symbolic references.
    • Access your variables with a real, non-symbolic reference based on the command-line argument. For example:
      my %name_to_hash = ( ISWTEST => \%ISWTEST, ISWLIVE => \%ISWLIVE, #...); while (($key,$value) = each %{$name_to_hash{$DB_name}}) { #...

    Give it a try and post a new question if you're still having trouble.

    -sam

Re: hash name as a variable?
by DrHyde (Prior) on Sep 16, 2004 at 08:44 UTC
    Don't EVER put a variable name in a variable.

    I created hash tables for each of these instances and then hit a snag. Instead of duplicating the code for each hash table I thought I would just have the one piece of code with the relevant hash table name as a variable.

    Have a single subroutine which takes a reference to the appropriate hash as one of its parameters.

    Incidentally, you really should reduce the code needed to demonstrate your problem to the bare minimum. You posted too much code, and I don't have the patience to read it.

      Incidentally, you really should reduce the code needed to demonstrate your problem to the bare minimum. You posted too much code, and I don't have the patience to read it.

      In that case, rather than complaining, we could teach the OP about <readmore> tags.

      When posting long messages - particularly those containing large chunks of code - it's often wise to enclose the 'long part' like this:

      <readmore> # long part </readmore>

      That way, a link is automatically created so we only have to see the enclosed text if we need to. This keeps the visible post much shorter and easier to digest. This and other useful info is available at Writeup Formatting Tips.

      Hope that helps!

      --J

      Update: Changed node title to better reflect content.

      Update 2: Changed node title again to 'link' to original thread.

        All "read more" would have done is hide some of the code. It wouldn't have cut the code down to the bare essentials which would have illustrated the OP's problem.
Re: hash name as a variable?
by amt (Monk) on Sep 15, 2004 at 15:23 UTC
      If my question was offensive in some way please feel free to give PRACTICAL criticism. Cheers, Ronnie
Re: hash name as a variable?
by hmerrill (Friar) on Sep 15, 2004 at 15:27 UTC
    Careful with your naming - "ISWTEST" should be "ISWTEST_PATH". Look again what you named your hashes up at the top.

    The one place you can be bitten by mis-spelling variable names is with *hashes* - even if you have "strict" and "warnings" on.

    HTH.

      I've looked & can see no error. There are 2 hash tables for each instance. ISWTEST & ISWTEST_PATH, ISWLIVE, ISWLIVE_PATH etc. Have I missed an obvious typo? Cheers, Ronnie
        My mistake - you didn't mis-spell anything.

        Did you get any output besides the error? And which line is line 224?