Perl 5.12 adds a feature that lets you locally delete a hash key or array element (refresh your memory of local with Item 43: Know the difference between my
and local
. This new feature allows you to temporarily prune a hash or an array:
delete local $hash{$key}; delete local $array[$index];
This syntax has actually been valid since at least Perl 5.6, although until Perl 5.12 it didn’t do anything different than a normal delete. Using it doesn’t even issue a warning:
$ perl5.6.2 -we 'delete local $ENV{PATH}'
This is documented in both in perlfunc and, curiously, perlsub
Temporarily delete hash keys
Setting up an inherited environment is one common reason that you would want to temporarily delete hash keys. You don’t want some values set when you run an external program:
{ delete local $ENV{DISPLAY}; system( 'some_program', @args ); }
Before Perl 5.12, you could temporarily delete hash keys in two steps. The first step requires you to assign the current hash to a new local version so you start off with all the current values. In the second step, you can delete the keys that you don’t want:
use Data::Dumper; %ENV = qw( PATH /usr/bin:/usr/local/bin PERL5LIB /Users/buster/lib/perl5 DISPLAY localhost:0 ); print_env( 'Top level = ' ); { local %ENV = %ENV; # Step 1 delete $ENV{PATH}; # Step 2 print_env( 'Inner level = ' ); } print_env( 'Back at top level = ' ); sub print_env { print @_, Dumper( \%ENV ), "\n"; }
The output shows you that the PATH
key temporarily disappears:
Top level = $VAR1 = { 'DISPLAY' => 'localhost:0', 'PERL5LIB' => '/Users/buster/lib/perl5', 'PATH' => '/usr/bin:/usr/local/bin' }; Inner level = $VAR1 = { 'DISPLAY' => 'localhost:0', 'PERL5LIB' => '/Users/buster/lib/perl5' }; Back at top level = $VAR1 = { 'DISPLAY' => 'localhost:0', 'PERL5LIB' => '/Users/buster/lib/perl5', 'PATH' => '/usr/bin:/usr/local/bin' };
Deleting a hash key is different from localizing the key and setting an undef
value (which is the same as not giving it a new value):
{ local $ENV{PATH}; print_env( 'Inner level = ' ); }
This doesn’t remove the hash key, which might be important:
Inner level = $VAR1 = { 'DISPLAY' => 'localhost:0', 'PERL5LIB' => '/Users/buster/lib/perl5', 'PATH' => undef };
The presence of a key, even with an undef
value, can mean something much different than the absence of a key. For instance, you might not get the default value your application might set when the hash key is missing.
Do it in one step with Perl 5.12
You can get the same thing with one step with Perl 5.12, and you get the same output as the first program:
use 5.012; use Data::Dumper; %ENV = qw( PATH /usr/bin:/usr/local/bin PERL5LIB /Users/buster/lib/perl5 DISPLAY localhost:0 ); print_env( 'Top level = ' ); { delete local $ENV{PATH}; # All together now print_env( 'Inner level = ' ); } print_env( 'Back at top level = ' ); sub print_env { say @_, Dumper( \%ENV ); }
This even works for a hash slice so you can temporarily remove several keys. The syntax works out nicely because there’s nothing else to set:
use 5.012; delete local @ENV{qw( PATH DISPLAY )};
Curiously, this also works with lexical hashes, which you might not expect since the syntax uses local. Even though you use a lexical hash, it’s the change to the hash that’s local. That’s a bit weird. You can rearrange the print_env
subroutine to be an anonymous subroutine defined at runtime so it can use the lexical variable:
use 5.012; use Data::Dumper; my %env = qw( PATH /usr/bin:/usr/local/bin PERL5LIB /Users/buster/lib/perl5 DISPLAY localhost:0 ); my $print_env = sub { say @_, Dumper( \%env ); }; $print_env->( 'Top level = ' ); { delete local @env{qw( PATH DISPLAY )}; # All together now $print_env->( 'Inner level = ' ); } $print_env->( 'Back in top level = ' );
Temporarily undefined array values
The delete local
syntax also works with arrays, although it’s a bit different from the hash effect. When you delete an array element, you just set the value at that index to undef
. That means that the array index is still there and the array does not change length (unless you’re deleting from the end):
use 5.012; use Data::Dumper; my @array = qw(buster mimi roscoe ginger ellie); my $print_array = sub { say @_, Dumper( \@array ); }; $print_array->( 'Top level = ' ); { delete local $array[2]; $print_array->( 'Inner level = ' ); } $print_array->( 'Back at top level = ' );
The output shows a hole in the array:
Top level = $VAR1 = [ 'buster', 'mimi', 'roscoe', 'ginger', 'ellie' ]; Inner level = $VAR1 = [ 'buster', 'mimi', undef, 'ginger', 'ellie' ]; Back at top level = $VAR1 = [ 'buster', 'mimi', 'roscoe', 'ginger', 'ellie' ];
If you temporarily want the array to not have an element, you’re in a bit of a pickle because you have to deal with package and lexical variables differently, although each requires you to make a copy.
Things to remember
If you have Perl 5.12 or later, you can:
- temporarily removes a hash key with
delete local $hash{$key}
. - temporarily undefined an array element with
delete local $array[$index]
. - apply
delete local
to lexical variables. - use
delete local
with slices.
Nice! Related and also useful would be Storable::dclone, as mentioned in Item 17.
Watch out for this is using perl compiled with -DPERL_USE_SAFE_PUTENV
I don’t think I’ve ever run into that. What happens is perl is compiled with -DPERL_USE_SAFE_PUTENV?
Using 5.12.1 with:
or
Some code that does this:
Math::Pari utils/Math/PariBuild.pm
Some code that did this:
Google-Chart 0.05014 t/90_env_proxy.t
No seg fault from:
or
or
or