views:

395

answers:

5

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.

+1  A: 

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.

Chas. Owens
hmm. I kinda want to be able to retrofit existing code. These flows can be very mysterious
mmccoo
Well, the question then becomes, how often does the string ENV appear in your code? Hopefully not often. You could then do a global search replace on the files and add a tie in a BEGIN block at the top.
Chas. Owens
+9  A: 

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 :-)

Roboprog
I posted a question to SO asking what should be done and you were the victim of a cyber-lynch mob. Siriram has put his examples from that book on CPAN here http://search.cpan.org/~sriram/examples/ and here is direct link to the example http://cpansearch.perl.org/src/SRIRAM/examples/Tie/Monitor.pm
Chas. Owens
Edit your answer and I will start you on your way back with a +1. BTW the best links for the book itself are http://oreilly.com/catalog/9780596004569/ (2nd edition) and http://oreilly.com/catalog/9781565922204/ (1st edition).
Chas. Owens
+1 for a bit of recovery, and I edited to clarify the book. The colophon in my Advanced Perl Programming is a black leopard. Blue Camel is Programming Perl (the camel is black, though the book has blue binding).
paxdiablo
+1 for taking some pain, if it doesn't kill you, it'll make you stronger ...
MrTelly
I'm glad I didn't write a nasty response (which I was *very* tempted to do). I seem to be back where I was, now. Anyway, as long as I have 50, I can post comments, so no harm done.
Roboprog
And thanks for providing better links, too.
Roboprog
The second edition of Advanced Perl is a very different book. (Perhaps 100% different from what I can tell.) I don't think that the Tie material is in the second edition. Chapter 9 of that edition is about inline code (eg, C in Perl).
Telemachus
+4  A: 

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;
}
jettero
Hmmm. Nice. If this works, then it would seem to imply that the only thing magical about %ENV is not how it is implemented (it's just an ordinary hash), but that it is referenced to build the "char * env[]" array by things that start external processes.
Roboprog
+2  A: 

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%`;
Axeman
+1  A: 

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( @_);
    }
Roboprog