How to "cast" string "1 + 2 + 3" to a number?

T

Todd Pytel

Hello again,

For various reasons that I won't go into here, I'm ending up with a string
like "1 + 2 + 3" that I'd like to evaluate as an arithmetic expression.
How can I "cast" that string to a number? For example, if I can get
$newnumber in the following snippet to equal 13 instead of 8, then I'm set:


#!/usr/bin/perl

my $string = "1 + 2 + 3";
print "String is $string.\n";
my $number = 7;
print "Number is $number.\n";
my $newnumber = $number + $string;
print "Newnumber is $newnumber.\n";

At the moment, I'm solving the problem using `expr`, but it seems very
nasty relying on the system for this.

Thanks,
Todd Pytel
 
W

Walter Roberson

:For various reasons that I won't go into here, I'm ending up with a string
:like "1 + 2 + 3" that I'd like to evaluate as an arithmetic expression.
:How can I "cast" that string to a number? For example, if I can get
:$newnumber in the following snippet to equal 13 instead of 8, then I'm set:

eval. But there's all kinds of security implications to that, so you
should also use the 'Safe' module [I think it is] to place restrictions
on what can be done.

If the only operation that is possible is addition, then you can
instead proceed by way of the substitute operator. You could
do something like match the pattern (\d+)\s*\+\s*(\d+) and
"substitute" the \1+\2 evaluated via the 'e' modifier of
the 's' operator.
 
D

David Oswald

Todd Pytel said:
For various reasons that I won't go into here, I'm ending up with a string
like "1 + 2 + 3" that I'd like to evaluate as an arithmetic expression.
How can I "cast" that string to a number? For example, if I can get
$newnumber in the following snippet to equal 13 instead of 8, then I'm
set:

my $string = "1 + 2 + 3";
my $result;
eval "\$result = $string;";

That's a real simple form. I escaped the $ on $result. The reason is that
I want the eval to see it as a variable, not as an undefined value through
variable interpolation. And it's also because of variable interpolation
that was able to put $string right inside the quotes of the eval.

But be very careful. If you take user input to generate that 1+2+3, you
expose yourself to the possibility of the user passing your script some
nasty code that could do all sorts of meanness.
 
S

Sam Holden

set:

my $string = "1 + 2 + 3";
my $result;
eval "\$result = $string;";

my $result = eval $string;

Is much "cleaner", in my opinion anyway.
But be very careful. If you take user input to generate that 1+2+3, you
expose yourself to the possibility of the user passing your script some
nasty code that could do all sorts of meanness.

This point can not be overstated. Doing this is almost always bad, unless
you really want arbitrary perl code to be executed (which sometimes you
do - but not in cgi, setuid or other such "run for others" places).
 
T

Todd Pytel

Got it - thanks for the quick replies. I was even looking at the perldoc
page for eval, but somehow it didn't appear to be what I wanted.

As for security, the context here is parsing config and output files for a
system monitoring utility, so there's no uncontrolled user input. And even
then, what's there has been validated pretty well. So I think I'm OK. But
I can certainly see the potential dangers in other contexts.

Thanks again,
Todd
 
T

Tore Aursand

For various reasons that I won't go into here, I'm ending up with a string
like "1 + 2 + 3" that I'd like to evaluate as an arithmetic expression.
How can I "cast" that string to a number? For example, if I can get
$newnumber in the following snippet to equal 13 instead of 8, then I'm set:

#!/usr/bin/perl

use strict;
use warnings;
my $string = "1 + 2 + 3";

Don't use double quotes if you don't have to. This is explained in the
Perl documentation.
print "String is $string.\n";
my $number = 7;
print "Number is $number.\n";
my $newnumber = $number + $string;
print "Newnumber is $newnumber.\n";

You could use 'eval()';

my $newnumber = $number + eval( $string );
 
U

Uri Guttman

SH> my $result = eval $string;

SH> Is much "cleaner", in my opinion anyway.

i agree and would do it that way if i were to use eval. but eval is
dangerous and should be avoided except for last resorts or when it just
makes for a better solution (eval "require $module" comes to mind).

so instead, just split on + (or -) and use sum. first wipe out the white
space.

<untested>

use List::Util ;

$string =~ tr/ //d ;

my $sum = sum( $string =~ /([+-]?\d+)/g ) ;

see ma, NO EVAL!

uri
 
U

Uri Guttman

TP> Got it - thanks for the quick replies. I was even looking at the perldoc
TP> page for eval, but somehow it didn't appear to be what I wanted.

so explain what you want. that statement is as bad as saying it doesn't
work. are we supposed to read your mind with PSI::ESP?

TP> As for security, the context here is parsing config and output
TP> files for a system monitoring utility, so there's no uncontrolled
TP> user input. And even then, what's there has been validated pretty
TP> well. So I think I'm OK. But I can certainly see the potential
TP> dangers in other contexts.

see my other post in this thread.

uri
 
K

ko

Todd said:
Hello again,

For various reasons that I won't go into here, I'm ending up with a string
like "1 + 2 + 3" that I'd like to evaluate as an arithmetic expression.
How can I "cast" that string to a number? For example, if I can get
$newnumber in the following snippet to equal 13 instead of 8, then I'm set:


#!/usr/bin/perl

my $string = "1 + 2 + 3";
print "String is $string.\n";
my $number = 7;
print "Number is $number.\n";
my $newnumber = $number + $string;
print "Newnumber is $newnumber.\n";

At the moment, I'm solving the problem using `expr`, but it seems very
nasty relying on the system for this.

Thanks,
Todd Pytel

eval() is an easy way to do it, but if $string is coming from outside
the program, another way to do it is using the Safe module:

perldoc Safe

As a simple example, the permit_only() and reval() methods limit which
operator(s) are allowed to run in a 'compartment' of Perl code.
Something like this:

use Safe;
my $safe = Safe->new;
$safe->permit_only( qw[ :base_core padany ] );
....
my $newnumber = $number + $safe->reval( $string );
....

That way $string (as described) is evaluated, but if $string contained
something like "unlink @list_of_files" or some other bad stuff you get
an error.

'perldoc Opcode' is also useful reading and recommended in the Safe
docs. While its not necessary to 'use' Opcode, the documentation
describes the operator name(s)/groups/sets - you usually want limit the
set of allowed operators to a minimum.

HTH - keith
 
T

Todd Pytel

TP> Got it - thanks for the quick replies. I was even looking at the perldoc
TP> page for eval, but somehow it didn't appear to be what I wanted.

so explain what you want. that statement is as bad as saying it doesn't
work. are we supposed to read your mind with PSI::ESP?

OK, let me rephrase that, though I don't find it unclear:

Eval works. It does exactly what I need, which, as stated in the OP, is to
"cast" a string containing an arithmetic expression to the numeric result
of that expression. I was thanking the responders for pointing me in the
direction of eval, and merely commenting that *I* had not recognized
its relevance when I looked at the perldoc description before the OP.
TP> As for security, the context here is parsing config and output
TP> files for a system monitoring utility, so there's no uncontrolled
TP> user input. And even then, what's there has been validated pretty
TP> well. So I think I'm OK. But I can certainly see the potential
TP> dangers in other contexts.

see my other post in this thread.

Duly noted. I'm confident that security concerns are adequately addressed
in this particular situation. In this particular situation, I will choose
simplicity over bulletproof invulnerability. If you want to read through
500 lines of code to see the small role this eval statement plays and
make your own judgment on it, I'll be happy to mail you the script.

--Todd
 
U

Uri Guttman

TP> Got it - thanks for the quick replies. I was even looking at the perldoc
TP> page for eval, but somehow it didn't appear to be what I wanted.
TP> OK, let me rephrase that, though I don't find it unclear:

TP> Eval works. It does exactly what I need, which, as stated in the OP, is to
TP> "cast" a string containing an arithmetic expression to the numeric result
TP> of that expression. I was thanking the responders for pointing me in the
TP> direction of eval, and merely commenting that *I* had not recognized
TP> its relevance when I looked at the perldoc description before the OP.

that is clearer. the timeline in your comments was tangled IMO.

TP> Duly noted. I'm confident that security concerns are adequately addressed
TP> in this particular situation. In this particular situation, I will choose
TP> simplicity over bulletproof invulnerability. If you want to read through
TP> 500 lines of code to see the small role this eval statement plays and
TP> make your own judgment on it, I'll be happy to mail you the script.

as i said, it is an informed choice. most newbies jump on eval (or
symrefs) for the simplest of tasks and wonder why we torch them for
that. if you understand what eval does, it dangers and security issues,
and you know where your eval input is coming from, then you are making
an informed decision. in stem i use eval to load config files written in
perl. also stem uses it to decode messages that were written with
Data::Dumper. both of those are places were eval is needed and were
informed decisions.

uri
 
A

Anno Siegel

Uri Guttman said:
SH> my $result = eval $string;

SH> Is much "cleaner", in my opinion anyway.

i agree and would do it that way if i were to use eval. but eval is
dangerous and should be avoided except for last resorts or when it just
makes for a better solution (eval "require $module" comes to mind).

so instead, just split on + (or -) and use sum. first wipe out the white
space.

<untested>

use List::Util ;

$string =~ tr/ //d ;

my $sum = sum( $string =~ /([+-]?\d+)/g ) ;

see ma, NO EVAL!

While you're pattern-matching...

( my $sum = $string) =~ tr/ //d;

1 while $sum =~ s/(\d+)([+-]?\d+)/$1 + $2/e;

Anno
 
T

Tad McClellan

[ snip: using string eval() ]
As for security, the context here is parsing config and output files for a
system monitoring utility, so there's no uncontrolled user input.


Bad guys can change the contents of files.
 
U

Uri Guttman

AS> Uri Guttman said:
use List::Util ;
$string =~ tr/ //d ;
my $sum = sum( $string =~ /([+-]?\d+)/g ) ;

AS> While you're pattern-matching...

AS> ( my $sum = $string) =~ tr/ //d;

AS> 1 while $sum =~ s/(\d+)([+-]?\d+)/$1 + $2/e;

amusing but SLOW! i mean, you are munging the string back and forth! and
sum is xs and fast. but it is still better than eval.

uri
 
B

Brian McCauley

[ snip: using string eval() ]
As for security, the context here is parsing config and output files for a
system monitoring utility, so there's no uncontrolled user input.


Bad guys can change the contents of files.

Including, of course, your Perl scripts.

Be sure you use a rational threat-model. However don't forget that
you may not always be the only user of the script and subsequent users
may not realise that the config files must be treated as executable
code from the point of view of a security audit.

To avoid the ambiguity I would suggest that when you want a script
that has a config file with embedded Perl code you turn it inside-out.
The 'script' becomes a Perl module and the 'config file' becomes a
Perl script that uses that module.

If "1 + 2 + 3" was coming from the output files for a system
monitoring utility then I would agree with Tad that feeding it into
eval would just be too risky. Unless of course you launder it...

/^([-+\.\d\s]+)/ or die;
my $sum = eval $1;

Note, the purpose of this simple laundering is not to ensure the string
can't contain an invalid expression, just to ensure it can't contain
anything dangerous.

--
\\ ( )
. _\\__[oo
.__/ \\ /\@
. l___\\
# ll l\\
###LL LL\\
 
A

Anno Siegel

Uri Guttman said:
AS> Uri Guttman said:
use List::Util ;
$string =~ tr/ //d ;
my $sum = sum( $string =~ /([+-]?\d+)/g ) ;

AS> While you're pattern-matching...

AS> ( my $sum = $string) =~ tr/ //d;

AS> 1 while $sum =~ s/(\d+)([+-]?\d+)/$1 + $2/e;

amusing but SLOW!

Oh, sure. By a factor of three, which isn't all bad, considering.
i mean, you are munging the string back and forth! and
sum is xs and fast. but it is still better than eval.

Well, /e *is* eval, kinda. /ee certainly is, without reservations, and
with the possible risks.

A single /e is safe. It says, basically, "treat the right side as a Perl
expression, not as a string". So the right side becomes code like other
code, and $1 and friends aren't more dangerous than other variables in Perl.

The second and further /ee... say "evaluate the result of the previous /e
as a string of Perl code". That is, in effect, what string-eval does, and
the implication is the same: Data is compiled and executed as Perl code.

Sorry for going on about this. Instinctively, most Perl programmers know
that /e is "safe" and /ee... isn't, but I haven't seen the difference
spelled out in so many words.

While I'm at it, let me go on a little more... Folklore has it that
multiple /e first worked by accident, a bug that was turned into a
feature. At first sight it looks strange that the entirely different
action of the second /e (calling eval) should happen by accident. But
that is just because the first /e's eval has already been called at run
time, it is the big eval that interprets the entire program. The actions
Perl takes for the first and further /e's may be very similar.

Anno
 
U

Uri Guttman

AS> Well, /e *is* eval, kinda. /ee certainly is, without reservations, and
AS> with the possible risks.

AS> A single /e is safe. It says, basically, "treat the right side as a Perl
AS> expression, not as a string". So the right side becomes code like other
AS> code, and $1 and friends aren't more dangerous than other variables in Perl.
AS> The second and further /ee... say "evaluate the result of the previous /e
AS> as a string of Perl code". That is, in effect, what string-eval does, and
AS> the implication is the same: Data is compiled and executed as Perl code.

AS> Sorry for going on about this. Instinctively, most Perl programmers know
AS> that /e is "safe" and /ee... isn't, but I haven't seen the difference
AS> spelled out in so many words.

/e is compiled at compile time so it is more like eval BLOCK. /ee's
second eval is runtime so it is like eval EXPR.

AS> While I'm at it, let me go on a little more... Folklore has it that
AS> multiple /e first worked by accident, a bug that was turned into a
AS> feature. At first sight it looks strange that the entirely different
AS> action of the second /e (calling eval) should happen by accident. But
AS> that is just because the first /e's eval has already been called at run
AS> time, it is the big eval that interprets the entire program. The actions
AS> Perl takes for the first and further /e's may be very similar.

i have heard that story too. but since the first /e is compile time it
probably does a recursive compile on the replacement text (eval). i
can't see how a second /e would have done anything at runtime when the
first was compile time. now this could be because when /e first was
added to s/// in some early perl, it was a runtime feature and then /ee
would just call eval twice. and in later perls, /e was converted to
compile time for speed and safety (the semantics wouldn't change
AFAICT).

uri
 
M

Martien Verbruggen

Don't use double quotes if you don't have to. This is explained in the
Perl documentation.

I don't agree with this sentiment. There is nothing wrong with the use
of double quotes there.

Could you tell me where in the Perl documentation this is explained,
so I can see what the reason is they give, and possibly submit a
patch, if I don't agree with that reason?

Martien
 
P

pkent

Todd Pytel said:
Got it - thanks for the quick replies. I was even looking at the perldoc
page for eval, but somehow it didn't appear to be what I wanted.

As for security, the context here is parsing config and output files for a
system monitoring utility, so there's no uncontrolled user input. And even
then, what's there has been validated pretty well. So I think I'm OK. But
I can certainly see the potential dangers in other contexts.

OK so the data is pretty certain to be OK and not malicious, but if
you're feeling like making sure:

use Safe;

And I believe that reval() and permit() are the methods you might like
to look at. E.g. this example code is used in an object (de)serializer
module (probably not totally relevant to you :)


require Safe;
my $c = new Safe;
#Minimum safe opcode set for building data structures
$c->permit_only(qw( rv2sv sassign aelem aelemfast helem anonlist
anonhash pushmark refgen const undef leaveeval ));
my $thawed = $c->reval($frozen);



P
 
P

pkent

AS> While you're pattern-matching...
AS> ( my $sum = $string) =~ tr/ //d;
AS> 1 while $sum =~ s/(\d+)([+-]?\d+)/$1 + $2/e;

amusing but SLOW! i mean, you are munging the string back and forth! and
sum is xs and fast. but it is still better than eval.

Heh :) OK it may well be slower that the List::Util solution but it's
still going to be fast.

I was going to make a comment along the lines of: "And if the expression
got really really long and really did slow stuff down then I'd blame the
user and get them to make their expr shorter"

But then I realised that we had exactly that situation at work:

User complains that program.pl is slow.
I write command-line wrapper to emulate CGI environment.
I finally narrow the problem down to a file which our XSSI-parser (it
rocks, and we will release it to CPAN, real soon now) is taking lots of
time over.

That file has an #if/#elif/#elif/.../#else staircase with __1350__
#elifs, and every time the program ran it had to go through all of them
before settling on the very last condition.

We suggested several different ways of improving that.

P
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,143
Latest member
DewittMill
Top