Question

I have a bunch of scripts that run tool flows. Like a Makefile does but in Perl.

As part of those flows, the Perl scripts set environment vars and it's not always easy to know when they happen and hence it can be hard to reproduce individual stages of the flow.

Is there a way to hook into %ENV such that I can register a callback when the environment changes?

Can I just tie to it? %ENV is already behaving like a tie.

Follow-up: Yes. You just tie to it.

Was it helpful?

Solution

This is doable. I think there's probably a performance penalty for doing the below, and I'm sure I didn't cover all the possible cases, but this should definitely get you started.

use strict;
use warnings;

tie %ENV, 'change_noticer', %ENV or die $!;

$ENV{PATH} .= ":test";
print $ENV{PATH}, "\n";
delete $ENV{PATH};

package change_noticer;

use strict;
use warnings;
use Carp;
use Tie::Hash;
use base 'Tie::StdHash';

sub DELETE {
    my $this = shift;

    carp "deleting \$ENV{$_[0]}";
    $this->SUPER::DELETE(@_);
}

sub STORE {
    my $this = shift;

    carp "altering \$ENV{$_[0]}";
    $this->SUPER::STORE(@_);
}

sub TIEHASH {
    my $class = shift;
    my $this  = bless {}, $class;

    while( my ($k,$v) = splice @_, 0, 2 ) {
        $this->{$k} = $v;
    }

    return $this;
}

OTHER TIPS

Ow. I just got my butt whooped. Anyway, there supposedly exists a monitor package that lets you monitor changes, via tie, to existing variables. This sounded like an interesting problem, so when I started digging into the "tie" documentation in The Blue Camel, there was no definition for what happens to an existing variable (i.e. - is a reference saved somewhere?). SO, I googled for "perl tie "existing variable"". Unfortunately, the link I found was socially unacceptable (pirated material), so I got pretty much slapped out of existence, reputation wise.

Good luck, though.

Anyway, just to clarify, it's in chapter 9 of "Advanced Perl Programming", the chapter on "Tie". Make sure you buy your copy from a reputable site :-)

Variable::Magic seems to work, although, unlike a tied hash, it doesn't seem to give you the value.

use 5.010;
use Carp            qw<carp>;
use Variable::Magic qw<cast wizard>;

my $magic_hash = wizard store => sub { 
    carp "Hey! They're trying to set $_[-1]!"; 
};

cast %ENV, $magic_hash;

$ENV{HOME} = '~evilhacker';

And if I later shell out, it seems to properly set the environment variable, so I didn't just clobber %ENV:

say `echo HOME=\$HOME`;
say `echo HOME=%HOME%`;

The only thing I can think of is to create a tied variable named %ENVIRONMENT that acts as an interface to %ENV that you can hook into. Then use make sure you always use %ENVIRONMENT instead of %ENV.

Another thing to try, reading between the lines: instead of calling "system(...)" for tasks to be run, implement a "my_system(...)" which prints out the environment before calling the external task, so that you could recreate one of the tasks with an "env ..." command from the command line

# show the environment in which an external command runs:
sub my_system
    {
    print map { $_ . "='" . $ENV{ $_ } . "'\n" } keys( %ENV);
    print "'" . join( "' '", @_) . "'\n";
    return system( @_);
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top