making runtime variable names global in perl - perl

OK, I've looked hard and have tried a myriad of variations to get this to work. I need some help from the perl experts.
First of all, I know, I know, don't use dynamic variable names! Well, I think I need to do so in this case. I'm fixing an inherited script. It has a set of variables that are formed with prefix + "_" + suffix. This started small, but prefix is now a list of about 20 and suffix of about 50 -- a thousand different variables.
The script in some cases loops over these variables, using eval to retrieve an individual value, etc.. This all works. The problem is, all uses of the different variables throughout the script, whether inside a loop on the prefix and/or suffix or just a hardcoded use, assume that these variables are globalized by using use var qw($varname). The number of use vars hardcoded statements has grown out of control. Moreover, there are thousands of past input profile files that initialize these variables using global syntax, all of which need to continue to work.
I was hoping I could improve maintainability by doing the globalization step in a loop, dynamically creating the variable names one at a time, then globalizing them with use vars or our. Here is some code to show what I'm after. I've shown several options here, but each is actually a separate trial. I have tried other variations and cannot keep them straight anymore.
my #prefixes = ("one", "two", ..., "twenty");
my #suffixes = ("1", "2", ..., "50");
my $globalvars = "";
for my $suffix (#suffixes) {
for my $prefix (#prefixes) {
# option 1 -- remainder of #1 outside of loop
# NOTE: 1.a (no comma); 1.b (include comma between vars)
$globalvars .= "\$$prefix\_$suffix, ";
# option 2
eval "use vars qw(\$$prefix\_$suffix)";
# option 3
my $g = "$prefix\_$suffix";
eval ("use vars qw(\$$g)");
# option 4
eval ("our \$$g");
}
}
# option 1.a
use vars qw($globalvars);
# or option 1.b
my (eval "$globalvars");
:
:
:
# now use them as global variables
if ($one_1 eq ...) {
if ($one_10) {
It seems that the problem with "use vars" (besides the fact that it is discouraged) is that the string inside qw has to be the actual variable name, not a dynamic variable name. The 'eval' options don't work, I think, because the scope only exists within the eval itself.
Is there a way to do this in perl?

string inside qw has to be the actual variable name, not a dynamic variable name
That's how qw works, it quotes words, it does no interpolation. You don't have to use qw with use vars, though.
#! /usr/bin/perl
use warnings;
use strict;
my #global_vars;
BEGIN { #global_vars = qw($x $y) }
use vars #global_vars;
# strict won't complain:
$x = 1;
$y = 2;
BEGIN is needed here, because use vars runs in compile time, and we need #global_vars to be already populated.

There's no need for eval EXPR since there's no need to use qw. qw is just one way to create a list of strings, and one that's not particularly useful to your needs.
my #prefixes; BEGIN { #prefixes = qw( one two ... twenty ); }
my #suffixes; BEGIN { #suffixes = 1..50; }
use vars map { my $p = $_; map { '$'.$p.'_'.$_ } #suffixes } #prefixes;

Related

How can I combine two strings and treat the result as the name of a variable?

How can I combine two strings and treat the result as the name of a variable?
I tried this:
my $container0 = "voila";
my $container1 = "ssss";
my $container2 = "swat";
my $container3 = int rand 2;
my $ans = '$container'."$container3";
print "$ans";
But it prints:
$container2
I want to print:
swat
(the value of the variable $container2). How can I do this?
First off - as the comments say read this:
http://perl.plover.com/varvarname.html
The way to do this is with a hash.
my %stuff;
$stuff{'container0'} = "voila";
$stuff{'container1'} = "ssss";
$stuff{'container2'} = "swat";
my $value = int rand 3;
my $ans = $stuff{'container'.$value};
print $ans,"\n";
(Needs to be 3 - int rand 2 will only ever give you 1 or 0).
However, that's perhaps a bit more complicated than you need.
Instead:
my #container = qw ( voila ssss swat );
print $container[rand #container],"\n";
For the sake of completeness - here is how you can do it:
YOU SHOULD NEVER EVER DO THIS, IT IS A TERRIBLE IDEA! IT WILL BREAK YOUR CODE IN ALL SORTS OF OBSCURE WAYS AND IS ENTIRELY REDUNDANT WHEN YOU HAVE HASHES
$container0 = "voila";
$container1 = "ssss";
$container2 = "swat";
$container3 = int rand 3;
$ans = 'container'."$container3";
print $$ans,"\n";
This will give you errors under strict and warnings.
Can't use string ("container1") as a SCALAR ref while "strict refs" in use
There is a good reason that it does this. I seriously, cannot re-iterate enough how terrible an idea it is to do this when you've perfectly good hashes available. There are all sorts of incredibly funky ways that clobbering random variables in your code can go disastrously wrong. Seriously - read the link above. It has some lovely examples. The best being if you accidentally tamper with some of the special variables, such as $* or $/ and screw up every regular expression or filehandle in the rest of your program - you'll be looking in the wrong place for the source of the error.
Edit: To follow on from a comment - you cannot use my if you do this - you need to use our. The easiest way to understand why, is just imagine that my renames a variable to a function unique name, and 'hides' it from the rest of the package. Because the symbolic ref evaluates at run time - perl can't pre-prepare when it's doing it's compilation and validation phases, so cannot 'see' the lexically scoped variable when it's running.
Using our would declare the variable in a broader scope (e.g. effectively global). So like this: (But it's still nasty)
use strict;
use warnings;
no strict "refs";
our $container0 = "voila";
our $container1 = "ssss";
our $container2 = "swat";
my $container3 = int rand 3;
my $ans = 'container'."$container3";
print $$ans,"\n";
Also refer to the FAQ for some more reasons it's not a good idea:
http://perldoc.perl.org/perlfaq7.html#How-can-I-use-a-variable-as-a-variable-name%3f
There is an issue with terminology here, where the term "interpret" is used:
What double-quotes do is called "interpolate". You can read about it here: http://perldoc.perl.org/perlop.html#Quote-and-Quote-like-Operators. This mostly supports some escape sequences and variable substitution.
If you are looking to print "swat" in the case of $container2, then that's called "evaluate". This treats the string as Perl code. To confuse things further, there is a way to evaluate an expression within an interpolated string: Can Perl string interpolation perform any expression evaluation?.
In answer to your question, you can use:
print eval ( $ans );
or
print "#{[ eval ( $ans ) ]}";
There is a good summary of all this here: http://www.perlmonks.org/?node_id=408346, and it also suggests using String::Interpolate as yet another solution.

2 Sub references as arguments in perl

I have perl function I dont what does it do?
my what does min in perl?
#ARVG what does mean?
sub getArgs
{
my $argCnt=0;
my %argH;
for my $arg (#ARGV)
{
if ($arg =~ /^-/) # insert this entry and the next in the hash table
{
$argH{$ARGV[$argCnt]} = $ARGV[$argCnt+1];
}
$argCnt++;
}
return %argH;}
Code like that makes David sad...
Here's a reformatted version of the code doing the indentations correctly. That makes it so much easier to read. I can easily tell where my if and loops start and end:
sub getArgs {
my $argCnt = 0;
my %argH;
for my $arg ( #ARGV ) {
if ( $arg =~ /^-/ ) { # insert this entry and the next in the hash table
$argH{ $ARGV[$argCnt] } = $ARGV[$argCnt+1];
}
$argCnt++;
}
return %argH;
}
The #ARGV is what is passed to the program. It is an array of all the arguments passed. For example, I have a program foo.pl, and I call it like this:
foo.pl one two three four five
In this case, $ARGV is set to the list of values ("one", "two", "three", "four", "five"). The name comes from a similar variable found in the C programming language.
The author is attempting to parse these arguments. For example:
foo.pl -this that -the other
would result in:
$arg{"-this"} = "that";
$arg{"-the"} = "other";
I don't see min. Do you mean my?
This is a wee bit of a complex discussion which would normally involve package variables vs. lexically scoped variables, and how Perl stores variables. To make things easier, I'm going to give you a sort-of incorrect, but technically wrong answer: If you use the (strict) pragma, and you should, you have to declare your variables with my before they can be used. For example, here's a simple two line program that's wrong. Can you see the error?
$name = "Bob";
print "Hello $Name, how are you?\n";
Note that when I set $name to "Bob", $name is with a lowercase n. But, I used $Name (upper case N) in my print statement. As it stands, now. Perl will print out "Hello, how are you?" without a care that I've used the wrong variable name. If it's hard to spot an error like this in a two line program, imagine what it would be like in a 1000 line program.
By using strict and forcing me to declare variables with my, Perl can catch that error:
use strict;
use warnings; # Another Pragma that should always be used
my $name = "Bob";
print "Hello $Name, how are you doing\n";
Now, when I run the program, I get the following error:
Global symbol "$Name" requires explicit package name at (line # of print statement)
This means that $Name isn't defined, and Perl points to where that error is.
When you define variables like this, they are in scope with in the block where it's defined. A block could be the code contained in a set of curly braces or a while, if, or for statement. If you define a variable with my outside of these, it's defined to the end of the file.
Thus, by using my, the variables are only defined inside this subroutine. And, the $arg variable is only defined in the for loop.
One more thing:
The person who wrote this should have used the Getopt::Long module. There's a major bug in their code:
For example:
foo.pl -this that -one -two
In this case, my hash looks like this:
$args{'-this'} = "that";
$args{'-one'} = "-two";
$args{'-two'} = undef;
If I did this:
if ( defined $args{'-two'} ) {
...
}
I would not execute the if statement.
Also:
foo.pl -this=that -one -two
would also fail.
#ARGV is a special variable (refer to perldoc perlvar):
#ARGV
The array #ARGV contains the command-line arguments intended for the
script. $#ARGV is generally the number of arguments minus one, because
$ARGV[0] is the first argument, not the program's command name itself.
See $0 for the command name.
Perl documentation is also available from your command line:
perldoc -v #ARGV

Recommended method to refactor variable name in Perl code?

I can use Perltidy to reformat source. Quite useful.
If a source file uses a variable like #m, how can I most easily refactor that into something else, e.g. #miles_travelled?
Using a regular expression to rename does not appear safe, because a separate variable such as $m may also exist (with a different type, in this case a scalar), yet the #m variable can be referenced using an expression like $m[$i].
For example, none of the following will be correct for Perl code:
s/([\$\#])m/$1miles_travelled/g # Will rename scalar with same name
s/\$m/\$miles_travelled/g # Will fail to rename accesses of array
Is there a recommended tool or method for safely renaming a variable name in Perl code?
The variable $m always occurs as $m.
The variable #m always occurs as #m or $m[...].
The variable %m always occurs as %m or $m{...} or #m{...}.
… except with indirect method calls: new $m[...] parses as $m->new([...]). But we can probably ignore this case (use no indirect to make sure).
If we want to cover the first three cases properly, we can
replace a scalar by s/(?<=\$)OLDNAME(?!\s*[\[\{])/NEWNAME/g
replace an array by s/(?<=\#)OLDNAME(?!\{)|(?<=\$)OLDNAME(?=\s*\[)/NEWNAME/g
replace a hash by s/(?<=\%)OLDNAME|(?<=[\$\#])OLDNAME(?=\s*\{)/NEWNAME/g
Note that lookarounds or multiple passes for the different cases are neccessary.
Test:
use Test::More tests => 3;
my $scalar_re = qr/(?<=\$) foo (?!\s*[\[\{])/x;
my $array_re = qr/(?<=\#) foo (?!\{) | (?<=\$) foo (?=\s*\[)/x;
my $hash_re = qr/(?<=\%) foo | (?<=[\$\#]) foo (?=\s*\{)/x;
my $input = '$foo, $foo[1], #foo, $foo{a}, %foo, #foo{qw/a b/}';
my $scalar = '$bar, $foo[1], #foo, $foo{a}, %foo, #foo{qw/a b/}';
my $array = '$foo, $bar[1], #bar, $foo{a}, %foo, #foo{qw/a b/}';
my $hash = '$foo, $foo[1], #foo, $bar{a}, %bar, #bar{qw/a b/}';
is $input =~ s/$scalar_re/bar/xrg, $scalar;
is $input =~ s/$array_re /bar/xrg, $array;
is $input =~ s/$hash_re /bar/xrg, $hash;
The Padre editor will carry out a small number of simple refactorings automatically for you. "Rename variable" is one of them.

Using # and $ with the same variable name in Perl

I am declaring the same variable name with # and $:
#ask=(1..9);
$ask="insanity";
print ("Array #ask\n");
print ("Scalar $ask\n");
Without using use strict I am getting output correctly but when I am using use strict it gives me a compilation error.
Do these two variables refer to two different memory locations or is it the same variable?
You've got two variables:
#ask
$ask
You could have %ask (a hash) too if you wanted. Then you'd write:
print $ask, $ask[0], $ask{0};
to reference the scalar, the array and the hash.
Generally, you should avoid this treatment, but the variables are all quite distinct and Perl won't be confused.
The only reason use strict; is complaining is because you don't prefix your variables with my:
#!/usr/bin/env perl
use strict;
use warnings;
my #ask = (1..9);
my $ask = "insanity";
my %ask = ( 0 => 'infinity', infinity => 0 );
print "Array #ask\n";
print "Scalar $ask\n";
print "Hash $ask{0}\n";
with use strict; you need to declare your variables first before using it.
For example:
use strict;
my #ask=(1..9);
my $ask="insanity";
print ("Array #ask\n");
print ("Scalar $ask\n");
#ask and $ask are different variables — as is %ask — and it is not an error to do this. It is however poor style.
Because the sigil changes when you use them, such as when you use $ask[1] to get the second element of #ask, the code becomes harder to read and use strict will also not be able to tell if you've gotten confused. Thus it's a good idea to use names that differ in more than the sigil unless you know what you're doing. So you could use e.g. #asks and $ask.
The error you are getting with strict is not due to variable names. It is because you are not declaring the variables (using one of my, our, local, or state. Nor are you using the vars pragma.
Short answer: Stick a my in front of each variable, and you'll be strict-compliant.
For package variables, you can examine entries in the symbol table. $ask and #ask are separate entities:
#!/usr/bin/env perl
use Devel::Symdump;
use YAML;
#ask=(1..9);
$ask="insanity";
my $st = Devel::Symdump->new('main');
print Dump [ $st->$_ ] for qw(
scalars
arrays
);
Among other things, this code will output:
--
…
- main::ask
…
---
…
- main::ask
…
Being able to use the same name can help when, say, you have an array of fish and you are doing something with each fish in the array:
for my $fish (#fish) {
go($fish);
}
Normally, it is more expressive to use the plural form for arrays and hashes, the singular form for elements of an array, and something based on the singular form for keys in a hash:
#!/usr/bin/env perl
use strict;
use warnings;
my #ships = ('Titanic', 'Costa Concordia');
my %ships = (
'Titanic' => {
maiden_voyage => '10 April 1912',
capacity => 3_327,
},
'Costa Concordia' => {
maiden_voyage => '14 July 2006',
capacity => 4_880,
},
);
for my $ship (#ships) {
print "$ship\n";
}
while (my ($ship_name, $ship_details) = each %ships) {
print "$ship_name capacity: $ship_details->{capacity}\n";
}

When does a global variable get its value assigned?

I encountered some strange behavior which hints that I do not understand some basic things about Perl script execution and initialization order. The following example:
#!/usr/bin/env perl
use strict;
use warnings;
print &get_keys(), "\n";
use vars qw/%hello/; # same effect with 'my %hello = (...);'
%hello = ( a => 1, b => 2 );
sub get_keys
{
return join(', ', sort keys %hello);
}
prints an empty string. Meaning that though variable is already visible, since the state with assignment wasn't yet reached, it has no value. (Using a scalar instead of the hash would trigger a warning about uninitialized variable.)
Is that intended behavior?
I would be also glad for the RTFM pointers.
From perlsub:
A my has both a compile-time and a
run-time effect. At compile time, the
compiler takes notice of it. The
principal usefulness of this is to
quiet use strict 'vars' .... Actual
initialization is delayed until run
time, though, so it gets executed at
the appropriate time.
# At this point, %hello is a lexically scope variable (the my took effect
# at compile time), but it still has no keys.
print get_keys();
my %hello = ( a => 1, b => 2 );
# Now the hash has content.
print get_keys();
sub get_keys { join ' ', keys %hello, "\n" }
Other notes: (1) You should be using my or our rather than use vars. (2) Under normal circumstances, don't call functions with a leading ampersand: use foo() rather than &foo().
Yes, it's intended behaviour. You call get_keys() before you assign to %hello, so %hello is empty in get_keys(). (Scalars are initialised to undef, while arrays and hashes are set to be empty by default.)
If you want %hello to be initialised immediately, use a BEGIN block:
use vars qw/%hello/;
BEGIN {
%hello = ( a => 1, b => 2 );
}
Note that if you were using my (or our, for that matter) then this wouldn't work:
BEGIN {
my %hello = ( a => 1, b => 2 );
}
because you have to declare the variable outside the block, like so:
my %hello;
BEGIN {
%hello = ( a => 1, b => 2 );
}
The use vars pragma predeclares global variable name at compile time. The variable is undefined unless you assign any value to it. Since you print it before the assignment, you rightfully get an empty string.
BTW, this pragma is obsolete. From perldoc vars:
NOTE: For variables in the current package, the functionality provided
by this pragma has been superseded by "our" declarations, available in
Perl v5.6.0 or later. See "our" in perlfunc.