Perl eval command not working as expected - perl

I have a sort of perl "terminal" (pastebin code) we'll call it that I've written, the idea behind writing it is I wanted to run perl code line by line, allowing me to run new commands on existing (large) data sets, without having to change a script and reload the data set and re-run my script.
(Mind you, I wrote this almost a year ago now, and it was mostly a learning experiment (with a dynamic function tablet), however now I have some use for it and discovered some issues which are preventing me from utilising it.)
As such, I eval user entered commands, however, they aren't behaving as expected and perhaps someone can shed some light on why this would be.
This is the 'important' bit, I have the command line data stored in #args, and the first element of that is stored in $prog. I check if there's an existing function (I allow users to create functions, and really abuse references to get an action table) if not I try and eval the command.
if(exists($actions{$prog})){
print "\n";
$actions{$prog}->(#args);
print "\n";
}else{
print "\nEVALing '$command'\n";
eval $command;
warn $# if $#;
print "\n";
}
As can be seen below, it works as expected for the assignment of scalars, but fails with the assignment of arrays and hashes.
user#host:~/$ perl term.pl
1358811935>$a = 0;
EVALing '$a = 0;'
1358811937>print $a;
EVALing 'print $a;'
0
1358811944>#b = qw(2 3);
EVALing '#b = qw(2 3);'
Global symbol "#b" requires explicit package name at (eval 5) line 1.
1358811945>print #b;
EVALing 'print #b;'
Global symbol "#b" requires explicit package name at (eval 6) line 1.
1358812008>my #b = qw(2 3);
EVALing 'my #b = qw(2 3);'
1358812008>print "#b";
EVALing 'print "#b";'
Possible unintended interpolation of #b in string at (eval 9) line 1.
Global symbol "#b" requires explicit package name at (eval 9) line 1.
1358812016>print join(',',#b);
EVALing 'print join(',',#b);'
Global symbol "#b" requires explicit package name at (eval 10) line 1.
1358812018>

Variables $a and $b are special, because they are used by sort. Therefore, strict does not complain if they are not declared. Using $x would trigger the same error as arrays and hashes.

For this kind of thing, you probably want to allow arbitrary package variables to be used by saying no strict 'vars';. Declaring a lexical (my) variable in the eval'd code will work, but will no longer be in scope for the next eval.
Alternatively, pre-declare a set of variables for the eval'd code to use (perhaps including a %misc hash).
A completely different approach is to each time through eval a concatenation of all the code entered so far (if printing output is a factor, redirecting output up until the most recent code entered).

Related

Declaring variables in Perl

I'm learning Perl, and facing some inconsistency between running a program from the command line versus interactively executing it interactively in the debugger.
Specifically, I invoke the Perl debugger with perl -d -e 1, and run this code line-by-line
my $a = 1;
print $a;
$b = 2;
print $b;
In the output I am only seeing the value of $b, while $a seems to be undefined. At the same time, when I execute the same statements with perl myscript.pl, both values are shown in the output. Why does this happen? What am I missing?
The debugger is a wholly different environment from run time Perl. Each line you enter behaves like a separate block, and if you declare a lexical variable like my $a then it will be deleted immediately after the command.
It is as if you had written
{ my $a = 1; }
{ print $a; }
{ $b = 2; }
{ print $b; }
Ordinarily you will declare lexical variables at an appropriate point in the program so that they don't disappear before you need them. But if you want to use the debugger to play with the language then you need to use only package variables, which never disappear and are what you get by default if you don't use my.
Command line "one-liner" Perl programs usually do the same thing, but it's a lesson you will have to unlearn when you come to writing proper Perl programs. You will be using use strict and use warnings at the head of every program, and strict requires that you choose between lexical or package variables by using my or our respectively. If you try to use a variable that you haven't previously declared then your program won't compile.
Also never use $a or $b in your code. Apart from being dreadful variable names, they are reserved for use by the sort operator.
I hope that helps.

strange thing in perl's eval function

I get the following warning:
"Use of uninitialized value in concatenation (.) or string at C:\tools\test.pl line 17, DATA line 1."
But the next line of __DATA__ will be processed without any warning and get these:
test1b.txt:test test1c.txt:test :test
More strange thing is that when I add a line: print "$line:".$'."\n"; The warning disappeared.
Anybody have some clues?
#!/usr/bin/perl -w
use strict;
my $pattern='test';
my $output='$&';
while(<DATA>)
{
chomp;
my $line=$_;
chomp($line);
$line=~/$pattern/;
#print "$line:".$&."\n"; #why uncommenting this line make the following line pass without no warning.
my $result="$line:".eval($output)."\n";
print $result;
}
__DATA__
test1a.txt
test1b.txt
test1c.txt
Perl considers $&, $', and $` to be expensive, so it won't actually populate them in a program that doesn't use them. From the perlvar manpage:
The use of this variable [$&] anywhere in a program imposes a considerable
performance penalty on all regular expression matches. To avoid this
penalty, you can extract the same substring by using #-. Starting
with Perl 5.10, you can use the /p match flag and the ${^MATCH}
variable to do the same thing for particular match operations.
However, when you only use them inside a string that you pass to eval, Perl can't tell that you're using them, so it won't populate them, so they'll be undefined.

Does Perl optimize based on specific arguments, while parsing the source code?

Is perl only checking for syntax errors during the parsing of the source code, or also doing some optimizations based on arguments/parameters?
E.g. if we run:
perl source.pl debug=0
and inside source.pl there is an if condition:
if ($debug == 1) {...} else {...}
Would the "precompilation/parsing" optimize the code so that the "if" check is skipped (of course assuming that $debug is assigned only at the beginning of the code etc, etc.)?
By the way, any idea if TCL does that?
Giorgos
Thanks
Optimizations in Perl are rather limited. This is mostly due to the very permissive type system, and the absence of static typing. Features like eval etc. don't make it any easier, either.
Perl does not optimize code like
my $foo = 1;
if ($foo) { ... }
to
do { ... };
However, one can declare compile time constants:
use constant FOO => 1;
if (FOO) { ... }
which is then optimized (constant folding). Constants are implemented as special subroutines, with the assumption that subs won't be redefined. Literals will be folded as well, so print 1 + 2 + 3 will actually be compiled as print 6
Interesting runtime optimizations include method caching, and regex optimizations.
However, perl won't try to prove certain properties about your code, and will always assume that variables are truly variable, even if they are only ever assigned once.
Given a Perl script, you can look at the way it was parsed and compiled by passing perl the -MO=Deparse option. This turns the compiled opcodes back to Perl code. The output isn't always runnable. When '???' turns up, this indicates code that was optimized away, but is irrelevant. Examples:
$ perl -MO=Deparse -e' "constant" ' # literal in void context
'???';
$ perl -MO=Deparse -e' print 1 + 2 + 3 ' # constant folding
print 6;
$ perl -MO=Deparse -e' print 1 ? "yep" : "nope" ' # constant folding removes branches
print 'yep';

How to avoid errors with perl command line parameters and use strict

Why is it that my code is not working after I added use strict; use warnings;? Is there a way to make it work?
Previously, the working code is:
#!/usr/bin/perl -s
print "x: $x\n";
print "y: $y\n";
The command that I ran is perl -s test.pl -x="hello" -y="world". The output is:
x: hello
y: world
However, after I added use strict; use warnings;, I got the following errors:
Variable "$x" is not imported at test.pl line 4.
Variable "$y" is not imported at test.pl line 5.
Global symbol "$x" requires explicit package name at test.pl line 4.
Global symbol "$y" requires explicit package name at test.pl line 5.
Execution of test.pl aborted due to compilation errors.
I know I need to declare my $x and my $y to fix the 3rd and 4th error. But what does the first 2 errors mean and how do I overcome it?
Actually, declaring these variables as lexical (my) variables will not help, because it's too "late": the -s switch will already have set them. It sets the global (package) variables (in your case, $main::x and $main::y, or — as a special shorthand — $::x and $::y). If you don't want to have to refer to them using their package-qualified names, then you can use an our declaration to indicate that the bare names $x and $y refer to the $x and $y in the current package:
our ($x, $y);
print "x: $x\n";
print "y: $y\n";
(Hat-tip to derobert for pointing out that you can use our for this.)
Alternatively, you can copy the global variables into identically-named lexical variables:
my ($x, $y) = ($::x, $::y);
print "x: $x\n";
print "y: $y\n";
This will take care of both sets of diagnostics.
You are using a a rudimentary switch parser perl -s, which uses global variables. To make it work with use strict you need to refer to the globals: $main::x as ruakh pointed out.
But even so, lexical variables (declared with my) are preferable in just about all cases. Just do:
use strict;
use warnings;
my ($x, $y) = #ARGV;
print "x: $x\n";
print "y: $y\n";
And use with:
perl test.pl hello world
For a more detailed and switch like handling, check out the Getopt::Long module.
To understand what ANY Perl error/warning means, you can refer to perldiag.
Specifically, for "is not imported", it says:
Variable "%s" is not imported%s
(W misc) With "use strict" in effect, you referred to a global variable that you apparently thought was imported from another module, because something else of the same name (usually a subroutine) is exported by that module. It usually means you put the wrong funny character on the front of your variable.
Basically, Perl made 2 distinct guesses about your un-declared identifyer $x - it was either
a package-scoped global that is prohibited from being used under strict ("Global symbol "$x" requires explicit package")
or it was an attempt to use another package's variable that ought to have been imported but wasn't ("Variable "$x" is not imported").
Perl can not tell WHICH of the two theories are correct, so spat out both possibilities. The latter error (Global symbol "$x" requires explicit package name) was the correct one in this case - it WAS a global variable in your original pre-strict code.

How can I eval environment variables in Perl?

I would like to evaluate an environment variable and set the result to a variable:
$x=eval($ENV{EDITOR});
print $x;
outputs:
/bin/vi
works fine.
If I set an environment variable QUOTE to \' and try the same thing:
$x=eval($ENV{QUOTE});
print $x;
outputs:
(nothing)
$# set to: "Can't find a string terminator anywhere before ..."
I do not wish to simply set $x=$ENV{QUOTE}; as the eval is also used to call a script and return its last value (very handy), so I would like to stick with the eval(); Note that all of the Environment variables eval'ed in this manner are set by me in a different place so I am not concerned with malicious access to the environment variables eval-ed in this way.
Suggestions?
Well, of course it does nothing.
If your ENV varaible contains text which is half code, but isn't and you give the resulting string to something that evaluates that code as Perl, of course it's not going to work.
You only have 3 options:
Programmatically process the string so it doesn't have invalid syntax in it
Manually make sure your ENV variables are not rubbish
Find a solution not involving eval but gives the right result.
You may as well complain that
$x = '
Is not valid code, because that's essentially what's occurring.
Samples of Fixing the value of 'QUOTE' to work
# Bad.
QUOTE="'" perl -wWe 'print eval $ENV{QUOTE}; print "$#"'
# Can't find string terminator "'" anywhere before EOF at (eval 1) line 1.
# Bad.
QUOTE="\'" perl -wWe 'print eval $ENV{QUOTE}; print "$#"'
# Can't find string terminator "'" anywhere before EOF at (eval 1) line 1.
# Bad.
QUOTE="\\'" perl -wWe 'print eval $ENV{QUOTE}; print "$#"'
# Can't find string terminator "'" anywhere before EOF at (eval 1) line 1.
# Good
QUOTE="'\''" perl -wWe 'print eval $ENV{QUOTE}; print "$#"'
# '
Why are you eval'ing in the first place? Should you just say
my $x = $ENV{QUOTE};
print "$x\n";
The eval is executing the string in $ENV{QUOTE} as if it were Perl code, which I certainly hope it isn't. That is why \ disappears. If you were to check the $# variable you would find an error message like
syntax error at (eval 1) line 2, at EOF
If you environment variables are going to contain code that Perl should be executing then you should look into the Safe module. It allows you to control what sort of code can execute in an eval so you don't accidentally wind up executing something like "use File::Find; find sub{unlink $File::Find::file}, '.'"
Evaluating an environment value is very dangerous, and would generate errors if running under taint mode.
# purposely broken
QUOTE='`rm system`'
$x=eval($ENV{QUOTE});
print $x;
Now just imagine if this script was running with root access, and was changed to actually delete the file system.
Kent's answer, while technically correct, misses the point. The solution is not to use eval better, but to not use eval at all!
The crux of this problem seems to be in understanding what eval STRING does (there is eval BLOCK which is completely different despite having the same name). It takes a string and runs it as Perl code. 99.99% this is unnecessary and dangerous and results in spaghetti code and you absolutely should not be using it so early in your Perl programming career. You have found the gun in your dad's sock drawer. Discovering that it can blow holes in things you are now trying to use it to hang a poster. It's better to forget it exists, your code will be so much better for it.
$x = eval($ENV{EDITOR}); does not do what you think it does. I don't even have to know what you think it does, that you even used it there means you don't know. I also know that you're running with warnings off because Perl would have screamed at you for that. Why? Let's assume that EDITOR is set to /bin/vi. The above is equivalent to $x = /bin/vi which isn't even valid Perl code.
$ EDITOR=/bin/vi perl -we '$x=eval($ENV{EDITOR}); print $x'
Bareword found where operator expected at (eval 1) line 1, near "/bin/vi"
(Missing operator before vi?)
Unquoted string "vi" may clash with future reserved word at (eval 1) line 2.
Use of uninitialized value $x in print at -e line 1.
I'm not sure how you got it to work in the first place. I suspect you left something out of your example. Maybe tweaking EDITOR until it worked?
You don't have to do anything magical to read an environment variable. Just $x = $ENV{EDITOR}. Done. $x is now /bin/vi as you wanted. It's just the same as $x = $y. Same thing with QUOTE.
$ QUOTE=\' perl -wle '$x=$ENV{QUOTE}; print $x'
'
Done.
Now, I suspect what you really want to do is run that editor and use that quote in some shell command. Am I right?
Well, you could double-escape the QUOTE's value, I guess, since you know that it's going to be evaled.
Maybe what you want is not Perl's eval but to evaluate the environment variable as the shell would. For this, you want to use backticks.
$x = `$ENV{QUOTE}`