tags:

views:

203

answers:

9

I'm trying to create what should be a simple little sub in Perl that preferably does not use any modules not found in the standard RedHat linux distribution. In other words the more portable the better because I cannot always control which system environment I'm working in. The obvious problem is passing the variables to the subroutine so that both the original variable name and the value can be used. I can get one or the other but can't figure out how to do both without a more complex input for the sub call as shown below. I could pass a string and a reference but that would be almost as messy as just printing it locally with a simple:

print "\$A = $A\n"; 

There are also potential scope issues but one step at a time. I'm now thinking maybe this isn't that simple.

(Yes this is absolutely lazy programmer code I'm looking for)

Example Pseudo Code:

my $A = 1;
my $secondVar = "something new";
my $XXX = 12345;

# Print a listing of the variables of interest in a nice easy to read listing
# with a minimum of typing. 

printVars( $A, $secondVar, $XXX ); 

# Note I could settle for passing by reference \$A but no more complicated than this in
# the sub call. This is just a little utility sub to use to double check variables while
# coding something new.

Output:

$A = 1
$secondVar = something new
$XXX = 12345

A rough SUB:

sub printVars {
    my @ListOfVars = @_;
    my $i;
    my ($theVarName, $theVarValue);

    for( $i=0; $i<@ListOfVars; $i++) {
       $theVarName = ??;  # This is where things break down.
       $theVarValue = $ListOfVars[$i];
       print "$theVarName = $theVarValue\n";
    }
}

Thanks for any help you can render..

Enjoy.. --Bryan

+1  A: 

you can pass the variable name as a scalar and then use referencing to print out the value. From perlref:

1.  $name = "foo";
2. $$name = 1; # Sets $foo
3. ${$name} = 2; # Sets $foo
4. ${$name x 2} = 3; # Sets $foofoo
5. $name->[0] = 4; # Sets $foo[0]
6. @$name = (); # Clears @foo
7. &$name(); # Calls &foo() (as in Perl 4)
8. $pack = "THAT";
9. ${"${pack}::$name"} = 5; # Sets $THAT::foo without eval
john
+1  A: 

Pass in a list of variable names, and then print them out with

foreach $varname (@list)
{
   print "$varname = $$varname\';
}

Actually, come to think of it, that won't work either, because $$ won't work because the variable won't be known to the sub. I don't think you'll be able to do what you want, short of putting all the variable names and values into a hash and dumping them with Data::Dumper. Normally at this point I'd delete this answer, but several other people have referenced it, so I won't.

use Data::Dumper;
print Dumper({"a" => $a, "cVar" => $cVar});

will produce something like:

$VAR1 = {
          'a' => 'foo',
          'cVar' => 'this is cVar'
        };
Paul Tomblin
Hmm.. The key to this sub was the very simple syntax into it. Has to be simpler than just a simple print statement. prtVar( $var1, $var2 )So if I used:prinVar( qw ($var1, $var2)) this would be close but the sub doesn't handle the list correctly. Have to think about this..--Bryan
Bryan Harding
What you're asking for, the ability to pass in a variable and then know what the variable name is, is impossible.
Paul Tomblin
OK Paul... Fair enough the sub would have no way to re-constrcut the original variable name from a pass by value. So passing the variable name would be the only solution but w/o the CPAN modules it appears that referencing the value of that variable name straight forward. Well I haven't worked through all the other code uploaded yet to see if any of it works.Thanks for the input...
Bryan Harding
@Paul: it's not impossible with modules such as PadWalker. However, I don't recommend everything that is possible. :)
brian d foy
+4  A: 

If the approach using variable names suggested by Paul Tomblin and john is acceptable, you may want to pull in the Data::Dumper module in order to be able to print out data structures.

Data::Dumper has been part of the standard Perl5 distribution for a long time (I just verified on CPAN that it has been in 5.6, 5.8, and 5.10). If your OS vendor hasn't taken that apart in bizarre ways, there's no need to worry about Data::Dumper not being there.

hillu
Code needs to be portable. I don't always have control over the perl build I'm running under.Thanks..--Bryan
Bryan Harding
You should always have Data::Dumper.
Schwern
Data::Dumper is part of the core Perl distribution and should be available on any Perl installation.
Dave Sherohman
edited my answer about availability of DD.
hillu
Most Excellent!!I just checked and OSX 10.6.2 Perl 5.10.1, RedHat Enterprise 4 Perl 5.8.5 and RedHat Enterprise 5 Perl 5.8.8 used on my server farm all seem to take use Data::Dumper so maybe I'm in better shape than I thought. Guess I've been doing this too long and missed all these cool new (well I guess not so new) additions...Thanks for that tip!! (As well as everyone who suggested Data:Dumper of course)Looks like I'll be able to Dump my variables...(pun intended)Thanks for all the help guys.
Bryan Harding
+1  A: 

See How can I use a variable as a variable name? for a viewpoint on using variables as variable names. Otherwise you have to use references as suggested by john and Paul Tomblin.

David Harris
Yeah I read that and respect the opinion but in this case this is a utility sub that I'm trying to construct. In general I wouldn't use variable names like this but for this purpose in the interest of keeping the sub call as simple as possible I think it's a requirement. --Bryan
Bryan Harding
+2  A: 

It really feels like you're trying to reinvent the wheel here, so I have to ask you for your motivations. There are plenty of existing mechanisms for helping you debug your code, including a built-in debugger (see perldoc perldebug and perldoc perldebugtut for the manual and tutorial), and a giant library of modules at CPAN. Anything you're thinking about building as a beginner has already been built, so unless you're doing this purely as a learning experience, you should use the tools that are already available to you.

Data::Dumper and Data::Dump are the usual ways of seeing the internals of a variable, not just simple strings as you are coding now but also deep and complicated data structures. Data::Dumper is part of many standard Perl installations, but anything distributed on CPAN is safe to depend on as it is easily installable on any system you are likely to encounter.

Ether
Well this has to do with the situations where the modules are not present and I'm not able to have them installed. In other words the environment is out of my control and or my customer isn't willing to add modules. Also working in other environments (Linux, OSX, Windows) each perl environment is a bit different and you can't always rely on the debuggers and modules being there. All that said, I started this exercise as what I thought would be an easy sub to write and ran into this wall. This should be easy and it doesn't seem to be.
Bryan Harding
@Bryan: your situation is a very commonly-asked question (e.g. see http://stackoverflow.com/search?q=[perl]+install+external+modules for many discussions of the various scenarios for installing modules), but you can always simply copy the source for Data::Dumper if you cannot ask a customer to install modules. But the debugger is part of native Perl so it is *always* available. (And no, dumping the contents of a variable isn't easy, as you will see by examining the source of Data::Dumper -- there are lots of edge cases to consider.) You are trying to reinvent the wheel here.
Ether
Yeah the dangerous part about modules that are not part of the standard distribution is that if you start using them and and branch out to where your code starts to depend on them, it makes the code more fragile in regard to transporting from one environment to another. Then you get into the require and module checking and it all goes crazy and your customer gets upset when they try to use the code elsewhere. I deal with it quite frequently when commercial EDA tools come in that have requirements that are not part of our environment. Makes you crazy but it's a living!!Thanks!!
Bryan Harding
OK I concede that DataDumper doesn't fall into that "dangerous" class that I mentioned above. I just lost track of time and all the modules that have been added to the core build in the last few years.
Bryan Harding
@Bryan: you may be interested in http://stackoverflow.com/questions/2049735/how-to-tell-if-a-perl-module-is-core-part-of-the-standard-install
Ether
Well, Data::Dumper has been core since 1998. :)
brian d foy
@brian: thanks; I wasn't on a machine where I could check that at the time I wrote my response, but I was pretty sure it's been core for at least 5.8.*. :)
Ether
A: 
# A sub to print the value of any variable, scalar, array, or hash
sub strv
{ # edit structured things as a string for debugging
  my ($v,$d) = @_;
  my $s = '';

  $d = 0 if not defined $d;

  if (not defined $v)
  {
    $s.= (' 'x$d)."N";
  }
  elsif (ref($v) eq 'SCALAR')
  {
    if (! defined $$v)
    {
      my $null = 'NULL';
      $v = \$null;
    }
    $s.= (' 'x$d)."S:$$v";
  }
  elsif (ref($v) eq 'ARRAY')
  {
    $s.= (' 'x$d)."A:(";

    my $c = '';
    my $x = 0;

    foreach my $a (map(strv($_,0),@{$v}))
    {
      $s.= "$c$x:$a";
      $c = ',';
      $x++;
    }
    $s.= ")";
  }
  elsif (ref($v) eq 'HASH')
  {
    $d++;
    $s.= "\n" if $s ne '';
    while( my ($k, $x) = each %{$v} ) {
      $s.= (' 'x$d)."H:".strv($k,$d+1).": ".strv($x,$d+1)."\n";
    }
    $s = substr($s, 0, -1);
  }
  elsif (ref($v))
  {
    $s.= ref($v);
  }
  $s.= (' 'x$d).'"'.$v.'"' if ref($v) eq '';
  return $s;
} # sub strv
Don
Hey Don... This looks like it requires Variable Name/ Data pairs to be passed to it. That's no all that much easier than a simple print statement from a syntax stand point when using the sub.Thanks..
Bryan Harding
+1  A: 

No sub, but I recommend Smart::Comments. It's as simple as this:

use Smart::Comments;
### $A

$A will be shown in all it's glory, if 1) it does not have a dynamic cycle (as some Win32::OLE objects do), or if 2) it's not an "inside-out object", which is a pointer to a key to the data and not the data itself. The output is even more readable than Data::Dumper (it uses Data::Dumper behind the scenes, and so DD globals are usable, like $Data::Dumper::Deparse (at the bottom of the linked list), for printing out subs.

And when you don't want to print out stuff, just comment out the use statement. Then, they are just comments.

It's quite portable, as well. I simply transplanted Text::Balanced and Smart::Comments from my PC to an AIX box and voila. Done.

Axeman
A: 

For the record here is an example of an actual solution as suggested by several of you using Data::Dumper. Hope it helps anyone looking for a simple sub to pretty print variables with very little effort. Thank you all!!

#!/usr/bin/perl -w
use strict;
use warnings;
use Data::Dumper;

my @myArray = qw ( a b c d e f g h i j k );

# Showing Difference between shift and pop and what happens to @myArray
my ($A, $B, $C, $D,);

$A = shift @myArray;
$B = pop   @myArray;
$C = shift @myArray;
$D = pop   @myArray;

# prtVar() usage is close to original desired simplicity 
# only needed to add the qw() 

prtVar(qw ($A $B $C $D));

sub prtVar
    {
    my (@vars) = @_;
    my $vname;

    foreach $vname (@vars)
        {
        print Data::Dumper->Dump([eval($vname)], [$vname]);
        }
    }

Output:

$A = 'a';
$B = 'k';
$C = 'b';
$D = 'j';
Bryan Harding
@Bryan => the reason this is working is because the `my` variables are in scope when `prtVar` is defined. if you move the `sub` declaration above the `my` variables, it won't work anymore. see my answer for a solution that will work with closed scopes (blocks, subs...)
Eric Strom
+2  A: 

I don't think this can be done for lexical variables without resorting to PadWalker, so you may need to install that. Assuming you can, here is a working solution that allows for minimal call site syntax.

use PadWalker qw/peek_my peek_our/;
use Data::Dumper;
$Data::Dumper::Indent = 0; # or however pretty you want

sub inspect {
    my $my  = peek_my  1;
    my $our = peek_our 1;

    for (split(/\s+/ => "@_")) {
        my $val = $$my{$_} || $$our{$_} 
                           || die "$_ not found";
        print Data::Dumper->Dump(
            /^\$/ ? ([$$val], [$_])
                  : ([ $val], ['*' . substr $_, 1])
        ) . "\n";
    }
}

sub foo {
    my $bar = shift;
    inspect '$bar';
}

{ # closed scope
    my $x = 'hello, world!';
    my $y;
    my @z = 1 .. 10;
    our %global = (a => 1, b => [1 .. 3]);
    my $ref = \%global;

    inspect '$x $y @z %global $ref';  # qw/.../ can be used also
    foo;
    foo $x;
}

this prints out

$x = 'hello, world!';
$y = undef;
@z = (1,2,3,4,5,6,7,8,9,10);
%global = ('a' => 1,'b' => [1,2,3]);
$ref = {'a' => 1,'b' => [1,2,3]};
$bar = undef;
$bar = 'hello, world!';
Eric Strom
PadWalker is so fattening but it tastes so good :)
brian d foy