Save, delete, and restore variables from/to a package's name space.

I came up with these after some experimentation when I recently needed to clear out a name space in order to "do" another script not in my control, then restore the defined variables much later.

Suggested improvements are welcome; this was my first time wading this deep into typeglobs and package namespaces.
# Return a hash that maps the name of symbols in a # namespace to an array of refs for all types for # which the name has a defined value. # A list of symbols may be specified; # default is all symbols in the name space. sub NameSpace::save { my $package = shift; my(%namerefs, $var, $type); no strict 'refs'; @_ = keys %{"${package}::"} if ! @_; foreach $var (@_) { $namerefs{$var} = []; my $fqvar = "${package}::$var"; # If the scalar for this variable name doesn't # already exist, *foo{SCALAR} will autovivify # the reference instead of returning undef, so # unlike the other types, we have to dereference # to find out if it exists. push(@{$namerefs{$var}}, *{$fqvar}{SCALAR}) if defined ${*{$fqvar}{SCALAR}}; foreach $type (qw(ARRAY HASH CODE IO)) { push(@{$namerefs{$var}}, *{$fqvar}{$type}) if defined *{$fqvar}{$type}; } } return \%namerefs; } # Remove the specified symbols from the namespace. # Default is to remove all. sub NameSpace::remove { my $package = shift; my(%namerefs, $var); no strict 'refs'; @_ = keys %{"${package}::"} if ! @_; foreach $var (@_) { delete ${"${package}::"}{$var}; } } # Restore values to symbols specified in a hash as returned # by NameSpace::save. sub NameSpace::restore { my($package, $namerefs) = @_; my($var, $ref); no strict 'refs'; foreach $var (keys %$namerefs) { my $fqvar = "${package}::$var"; foreach $ref (@{$namerefs->{$var}}) { *{$fqvar} = $ref; } } }

Replies are listed 'Best First'.
RE: package namespace manipulation
by Dominus (Parson) on Nov 14, 2000 at 23:44 UTC
    You wrote:
        
    >    push(@{$namerefs{$var}}, *{$fqvar}{SCALAR})
    >                      if defined ${*{$fqvar}{SCALAR}};
    

    Perhaps I am mistaken, but I believe that *{$foo}{SCALAR} is always defined. Try for example:

    perl -wle 'print "Yep" if defined *{"NOSUCH"}{SCALAR}'
      You're right, but that's why I'm dereferencing it to find out if there's a live SCALAR at the end. Your example doesn't actually match what's coded; try:
      perl -wle 'print "Yep" if defined ${*{"NOSUCH"}{SCALAR}}'
        Oh, my mistake; thanks for the correction.

        It still seems to me that it would be simpler to write defined ${$fqvar} instead of defined ${*{$fqvar}{SCALAR}} however.

RE: package namespace manipulation
by AgentM (Curate) on Nov 14, 2000 at 23:13 UTC
    If you threw this into a package, you would be able to have your own namespace to store vars in and you could throw it onto CPAN.
    AgentM Systems nor Nasca Enterprises nor Bone::Easy nor Macperl is responsible for the comments made by AgentM. Remember, you can build any logical system with NOR.
RE (tilly) 1: package namespace manipulation
by tilly (Archbishop) on Nov 14, 2000 at 23:18 UTC
      For a lot of reasons that I won't bore you with, I have to "do" a number of scripts in the same package, but protect them from stomping on each other. The logical flow is:
      package my_package; for my $script (@scripts) { do $script; $vars{$script} = NameSpace::save('my_package'); NameSpace::delete('my_package'); } # additional processing here for my $script (@scripts) { NameSpace::restore('my_package', $vars{$script}); # additional script-specific processing }
      I don't see how you'd accomplish with local, but if you know of a way, I'm all ears.
        I thought it was as easy as:
        local *private::package::; # time passes $saved{$some_name} = \*private::package::;
        but I was wrong.

        As for the underlying question though, your description of what you are doing sets off warning flags for me. Why not scoop out the body of the scripts into modules, turn the scripts into wrappers, and then call the module in your code? What if a script makes an assumption and does something like call exit? Why do you need to have the globals in your current package, can you redesign to not need that? If you have any control over what globals you need from the script, why not eval each script into its own package, then in your second loop alias just the globals that you need from the scripts?

        The exit question is a serious gotcha. The other suggestions are alternate ways of solving your likely problem. I have found valid uses for all of the other suggestions. The one with each getting their own package was for configuration loading - there are very few other places where I would even consider that. The idea of scooping things into modules is my first recommendation.

        Now there could be things I don't know that make all of these ideas bad. But my gut feeling is that you don't need to use your current design, and that using it you will run into further issues - it just smells like a bad hack.