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

Hello Monks, yesterday I posted a question about refactoring code for a company and now I have another question about doing so. The previous writers of the code never used use strict/use warnings so I'm trying to make it so that the code uses these. Needless to say the moment I add them a bunch of scope errors get thrown. My question is such, there is a script that contains purely hashes that contain "global" info. Such as work directories, server names, departments, etc. These hash structures are modified by a subroutine that reads a config file and adds entries to the hash to reflect the contents of the config file, and then this configured hash is used in the main automation scripts. It works currently without strict/warnings but when I turn it on it complains. I believe I read something about packages and using "our" for the hashes but I haven't read anything specific enough to my issue to trust it completely. Can anyone help me set up the files correctly for this kind of processing? e.g. 1. Global hash lives in a separate folder, 2. Said hash is modified by a subroutine only once when scripts called(this was done by a config subroutine that lives in a script that was required by another script, I believe that when this script was required it invoked the subroutine and configured the hash), 3. this global hash is properly accessible by all other automation scripts. Thank you for any insight I greatly appreciate it. EDIT(Sorry for not being detailed enough): So I have a hash that lives in one script, say global.pl, it looks like this:
%GLOBAL = { dir1 => "/path/to/directory" dir2 => "/path/to/another" }
Then I have another script that contains a subroutine to modify this hash, as well as a call to said subroutine, I believe that this call gets run when the script is "required". This is how that script looks, lets call it config.pl:
[requires at the top] read_config_files(); sub read_config_files(){ #Do operations that read .txt files and load #info into the GLOBAL hash structure. }
Now these previous scripts are never explicitly called, the main automation script looks like this, let's call it main.pl:
#A few requires... require "global.pl"; require "config.pl"; my first_directory = $GLOBAL{dir1}; etc...
So that after the config.pl is "required" it executes the read_config_files subroutine and the %GLOBAL hash looks like this:
%GLOBAL = { dir1 = "/path/to/directory", dir2 = "/path/to/another", newstuff = "a string that was added", }
But I get a "Global symbol "GLOBAL" requires explicit package name at main.pl line xx. My question is how can I properly have this working so that the error does not get thrown. Or, how can I convert the way this is working. Note that it is not just main.pl that tries to access members of %GLOBAL this way, there are a few other scripts that get their global information from this hash, therefore I'm trying to only have on copy of the hash and centralize all the info to that hash. UPDATE: I've managed to fix the problem by putting the hashes into a package! Thanks for all your help

Replies are listed 'Best First'.
Re: Modifying/Accessing hash in separate script
by toolic (Bishop) on Jul 22, 2015 at 18:54 UTC
    Completely unrelated to your main question...
    never used use strict/use warnings so I'm trying to make it so that the code uses these. Needless to say the moment I add them a bunch of scope errors get thrown
    strictv -- how unstrict is your code? might make the task look less daunting.
Re: Modifying/Accessing hash in separate script
by Laurent_R (Canon) on Jul 22, 2015 at 18:51 UTC
    Please show your code or a subset thereof, it is next to impossible to answer your question without having a deeper understanding of what this code does.

    In general terms, I do not think that you need to use the our built-in for what you describe.

    Not even if you really need a global hash. You can just make a "global" hash by just declaring it with my at the top-level of your program file (outside of any specific lexical scope).

    Or do you have several code files, with use or require relations between them?

      Updated for your pleasure!
        Alright, you've now changed your original post, so that it is now clear to me that you have a main script and a required module. Maybe that was clear before your changes, but, if so, I clearly did not pick it up.

        Well, in this case, yes, using the our function (instead of my) does make sense, it can make the variable available in both code files. Sorry if my previous answer was wrong, I did not understand the full context.

        Although, to tell the truth, I am almost never using our to share variables between different compile units. I tend to prefer to create setter and accessor functions and to export explicitly these subroutines, rather than the variables.

        You should be able to scope that global with our

        our %GLOBAL = { dir1 => "/path/to/directory" dir2 => "/path/to/another" };
        You will most likely have more errors to contend with that are not necessarily related. Just deal with them one at a time. (And check in your code each time you successfully remove a compilation error or warning.)

        UPDATE!
        Why is that hash holding a hash reference? It probably should be either

        our %GLOBAL = ( dir1 => "/path/to/directory" dir2 => "/path/to/another" );
        or
        our $GLOBAL = { dir1 => "/path/to/directory" dir2 => "/path/to/another" };
        I sincerely hope that was a typo on your behalf, this looks to be a daunting piece of code to refactor.

        jeffa

        L-LL-L--L-LL-L--L-LL-L--
        -R--R-RR-R--R-RR-R--R-RR
        B--B--B--B--B--B--B--B--
        H---H---H---H---H---H---
        (the triplet paradiddle with high-hat)
        
Re: Modifying/Accessing hash in separate script
by NetWallah (Canon) on Jul 22, 2015 at 18:39 UTC
    Try to LOCK the hash after it has the config info loaded.

    Hash::Util has the lock_hash_recurse method to enforce such a thing.

    That way, you can catch offenders that try to modify the config, and take appropriate action.

            "Despite my privileged upbringing, I'm actually quite well-balanced. I have a chip on both shoulders."         - John Nash

      I'm afraid you may have misinterpreted what I'm trying to accomplish. There's only going to be one subroutine modifying the has, that's okay. It's a matter of satifying the use strict/use warnings when I try to access members of the hash in other scripts.

        Can you post a bit of how the hash is declared in the one script, and how it is used in the other?

        Maybe you have

        # config.pl $config{ base_dir } = '/opt/myapp'; $config{ bin_dir } = "$config{ base_dir }/bin";

        ... and then in the main program(s):

        require 'config.pl'; print "My config is $config{ base_dir }\n";

        Then you can add use strict to both files if you add use vars '%config'; to both of them.

Re: Modifying/Accessing hash in separate script
by Anonymous Monk on Jul 23, 2015 at 01:17 UTC
    You can actually give explicit package name to satisfy strict (like it says). Like this:
    %main::GLOBAL = ( dir1 = "/path/to/directory", dir2 = "/path/to/another", );
    and then in read_config_files:
    sub read_config_files { $main::GLOBAL{newstuff} = "a string that was added"; # etc }
    That's assuming that %GLOBAL lives in the main package, which I suppose it does. For refactoring, perhaps it would be clearer to give it its own package:
    # this is file global.pl use strict; use warnings; package GlobalStuff; %GlobalStuff::GLOBAL = ( dir1 = "/path/to/directory", dir2 = "/path/to/another", ); # or, alternatively (same thing) our %GLOBAL = ( dir1 = "/path/to/directory", dir2 = "/path/to/another", );
    and
    # this is file config.pl use strict; use warnings; require 'global.pl'; read_GLOBAL(); print_GLOBAL(); # for example sub read_GLOBAL { $GlobalStuff::GLOBAL{new} = 'a string'; } sub print_GLOBAL { while ( my ($k, $val) = each %GlobalStuff::GLOBAL ) { print "$k => $val\n"; } }
    Then I would rename 'global.pl' to 'GlobalStuff.pm', and change all calls to require accordingly:
    require GlobalStuff;
    require appends .pm to a bareword and searches this file in @INC; see require for details.
      If I do this approach will it allow the read_config_files subroutine to modify the hash and the changes will be reflected across all other scripts that access the hash?
        Yes, it should work (but why don't you test it?).

        Having said that, having a global hash shared among different program files is usually rather poor design. I can't say for sure, because we don't have enough details about what you are doing, but maybe you should share functions accessing to this hash (useing a module), or perhaps think about an OO-implementation.