Temporarily remove hash keys or array elements with `delete local`

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.
Leave a comment

4 Comments.

  1. Nice! Related and also useful would be Storable::dclone, as mentioned in Item 17.

  2. Watch out for this is using perl compiled with -DPERL_USE_SAFE_PUTENV

  3. I don’t think I’ve ever run into that. What happens is perl is compiled with -DPERL_USE_SAFE_PUTENV?

  4. Using 5.12.1 with:

      config_args='-Accflags=-DPERL_USE_SAFE_PUTENV'
      ccflags ='' -DPERL_USE_SAFE_PUTENV'
      cppflags=' PERL_USE_SAFE_PUTENV'
    
    $ perl -e 'local %ENV;delete $ENV{q{UNDEFINED}}'
    

    or

    $ DEFINED=YES perl -e 'local %ENV;delete $ENV{q{DEFINED}}'
    
    Segmentation fault (core dumped)
    
    Process terminating with default action of signal 11 (SIGSEGV): dumping core
    
    Invalid read of size 8
      unsetenv (setenv.c:295)
      by Perl_magic_clearenv
    
    perlsub
      Temporary Values via local()
        Localized deletion of elements of composite types
          "The behavior of local() on non-existent members of composite types is subject to change in future."
    

    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:

    $ DEFINED=YES perl -e 'delete local $ENV{q{DEFINED}}'
    

    or

    $ perl -e 'delete local $ENV{q{UNDEFINED}}'
    

    or

    $ DEFINED=YES perl -e 'local %ENV=%ENV;delete $ENV{q{DEFINED}}'
    

    or

    $ perl -e 'local %ENV=%ENV;delete $ENV{q{UNDEFINED}}'
    

Leave a Reply


[ Ctrl + Enter ]

7ads6x98y