I am just getting back into Perl programming so I appologize if this is an easy/stupid question.
My If statement is returning true (never going to the else) and I'm not really sure why. For example this code never prints "getshere":
#showName = ("Matt","Matt","Matt","Gym","Gym");
$counter=0;
foreach (#showName)
{
if ($showName[$counter]==$showName[$counter+1])
{
print "$showName[$counter] equ $showName[$counter+1]\n";
}
else
{
print "getshere";
}
$counter++;
}
Can you please tell me what I am doing wrong?
Thank you!
== is numeric comparison, and strings like 'Matt' and 'Gym' all have the numeric value 0, so they're all == to each other. For string comparison, use eq instead:
if ($showName[$counter] eq $showName[$counter+1])
(I recommend enabling warnings, by the way, by adding use warnings; near the start of your script. Had you done that, you would have received a helpful message warning you that you were applying a numeric equality-test to non-numeric values.)
The == operator converts strings to numbers and does numeric comparison. To compare strings, use eq. To test if strings are greater or less than eachother, use cmp.
if ($showName[$counter] eq $showName[$counter+1])
Related
I'm new to Perl. I want to understand why does == operator is treating both these strings alike? It works ok if I use eq instead if ==. If name is kuldeep or rahul, it prints 'Right name'.
my $name="kuldeep";
if ($name == "rahul")
{
print 'Right name!',"\n";
}
else
{
print 'Wrong name!','\n';
}
You are mistaken. The numerical equality comparison operator works perfectly fine with strings!
$ perl -e'CORE::say "123" == "123.0" ? "same" : "different"'
same
$ perl -e'CORE::say "123" == "123.1" ? "same" : "different"'
different
In your example, you are asking Perl to compare the numerical value of the string kuldeep (zero with a warning) with the numerical value of the string rahul (zero with a warning), and they are indeed equal.
ALWAYS USE use strict; use warnings;!!!
And use eq to compare strings.
Interpreter realizes (from the == operator) that it's doing a numeric comparison. The value of $name is converted to a numeric, which gets you a 0. "rahul" is converted to a numeric, which is a 0. 0 == 0, so that's true, and thus "Right name" is chosen.
If I compare it against my name, works the same way.
However, if there really is a string with a number in it, like you made either string "12345" (specifically with the quotes), Perl will assume you knew what you were doing by requesting the == operator, and will dutifully auto-convert ("cast" in programmer-speak) that to numeric 12345. Then your comparison will fail.
TL/DR: use 'eq' for string comparison! :-)
Edit: I did try using eq instead of == earlier, and it did not work. For some reason now it does. It's possible that there was another error at that point which prevented it from working, but now this has been resolved. Thank you.
I'm trying to do some simple validation. I want to make sure the redirect url being fed through a variable begins with a particular site or not. If it does, the redirect goes through, if not, it redirects to the root of the site. Seems pretty straight forward, right?
$redir = $input{'redirect'};
$redir_sub = substr($redir, 0, 21);
if ($redir_sub == "http://www.mysite.com") {
print "Location: $redir \n\n";
}else{
print "Location: http://www.mysite.com \n\n";
}
The thing is, no matter what variable I place in there, it the "if" returns as true. I could put my favorite webcomic in there and it'll redirect to it, despite the string not matching. For example this:
$redir = $input{'redirect'};
$redir_sub = "http://www.yahoo.com"
if ($redir_sub == "http://www.mysite.com") {
print "Location: $redir_sub \n\n";
}else{
print "Location: http://www.mysite.com \n\n";
}
That redirects to yahoo! What is going on?
if ($redir_sub == "http://www.mysite.com")
should be
if ($redir_sub eq "http://www.mysite.com")
as eq is string equality operator, and using == forces number comparison so in this case condition always evaluates to trues as 0 == 0 is true.
Operator == is used to compare numbers. You should replace it with operator eq
TL;DR: use eq not ==!
Perl as a language seems to have a problem: It uses the same data type (the scalar) for a lot of different things, including strings and numbers. A scalar is both at the same time, not just one of those. Perl has no type annotations, and there is no way to indicate if a variable holds a string or a number.
This produces problems when we consider equality tests. Assuming two scalars $a = "42.0" and $b = 42. Are they equal? Yes and no:
The strings "42" and "42.0" are not the same thing! These are not equal.
The numbers 42 and 42.0 are equal!
As indicated above, Perl does not use a type system to solve this ambiguity. Rather, it uses different sets of operators for string and numeric operations:
eq ne lt le gt ge cmp
== != < <= > >= <=>
Your problem is that you are not using
use warnings;
Which is why Perl is allowing you to make this mistake. You are using the numeric equality operator to compare strings. Hence, Perl first tries to convert each parameter to a number. And since your strings do not begin with numbers (and do not look like numbers), they are converted to zero 0. Hence your expression
if ($redir_sub == "http://www.mysite.com")
Really means this
if (0 == 0)
Which of course always returns true.
If you had been using warnings, you would have gotten the errors:
Argument "http..." isn't numeric in numeric eq (==) at ...
Argument "http..." isn't numeric in numeric eq (==) at ...
Which would have been a hint as to your problem that you should be using eq and not == to compare strings.
== does numeric comparison. eq does string comparison. When you use a string as a number, perl passes your string through your c library's aton(). So you're really asking your computer if 0 == 0 which is true.
I'm new to perl and I've mainly worked with php in the past,
usually to check if a get variable is equal to something and then do something else if it is, i'd simply do
if ($_GET['page'] == 'news') {
echo 'yessss';
}
but I'm not really sure what to do with perl :$ could anyone help me out? :)
Thanks!
In Perl, the module of CGI exists,
http://perldoc.perl.org/CGI.html#SYNOPSIS
and ->param gets an input parameter.
use CGI;
my $q = CGI->new;
print $q->header();
if ($q->param('page') eq 'news') {
print "...";
}
In perl you have different operators for checking string versus integer equality
"==" returns true if the left argument is numerically equal to the right argument.
"eq" returns true if the left argument is stringwise equal to the right argument.
So I have to make a simple calculator in Perl that maintains an accumulator and does simple operations. The accumulator starts at 0 and then changes based on the results that I receive. At the moment I am only trying to get addition to work. When I check to ensure that the operator entered is + something goes wrong. For instance:
Accumulator: 0
Operator: Anything put here results in addition. Including this sentence.
Operand: 4
Accumulator: 4
It catches numbers but nothing else. I have tried using grep and a list of the operators. I have exhausted all of my ideas. Here is my code (Fyi first post so help me with any noob errors):
my $running = 1;
my $accum = "0";
my $operator;
my $operand;
print("Welcome to the simple, command line calculator.\n");
print("To terminate, press Control-C.\n\n");
while ($running){
print("\nAccumulator: ".$accum."\n");
print("Operator: ");
$operator = <STDIN>;
if ($operator == "+"){
print("Operand: ");
operand = <STDIN>;
$accum += $operand;
}
else{
print("Invalid operator: ".$operator."\n");
}
}
Perl doesn't remove the ending newline from input unless you use the -l option, so you're comparing "+" against "+\n". Usually you want to do soemthing like chomp($operator);.
That said, your real problem is that == does numeric comparison, and both "+" and "+\n" evaluate to 0 in numeric context. (Using -w, as you should always do, would warn you about this.) Use the eq operator for string comparison.
== compares numbers, not strings. If you compare strings, the strings will be converted to numbers; for non-numeric strings, this means they become 0. So $operator == "+" becomes 0 == 0.
For strings, use eq instead. Additionally, keep in mind that <STDIN> will preserve newlines; make sure to chomp $operator as well.
Is there a simple way in Perl that will allow me to determine if a given variable is numeric? Something along the lines of:
if (is_number($x))
{ ... }
would be ideal. A technique that won't throw warnings when the -w switch is being used is certainly preferred.
Use Scalar::Util::looks_like_number() which uses the internal Perl C API's looks_like_number() function, which is probably the most efficient way to do this.
Note that the strings "inf" and "infinity" are treated as numbers.
Example:
#!/usr/bin/perl
use warnings;
use strict;
use Scalar::Util qw(looks_like_number);
my #exprs = qw(1 5.25 0.001 1.3e8 foo bar 1dd inf infinity);
foreach my $expr (#exprs) {
print "$expr is", looks_like_number($expr) ? '' : ' not', " a number\n";
}
Gives this output:
1 is a number
5.25 is a number
0.001 is a number
1.3e8 is a number
foo is not a number
bar is not a number
1dd is not a number
inf is a number
infinity is a number
See also:
perldoc Scalar::Util
perldoc perlapi for looks_like_number
The original question was how to tell if a variable was numeric, not if it "has a numeric value".
There are a few operators that have separate modes of operation for numeric and string operands, where "numeric" means anything that was originally a number or was ever used in a numeric context (e.g. in $x = "123"; 0+$x, before the addition, $x is a string, afterwards it is considered numeric).
One way to tell is this:
if ( length( do { no warnings "numeric"; $x & "" } ) ) {
print "$x is numeric\n";
}
If the bitwise feature is enabled, that makes & only a numeric operator and adds a separate string &. operator, you must disable it:
if ( length( do { no if $] >= 5.022, "feature", "bitwise"; no warnings "numeric"; $x & "" } ) ) {
print "$x is numeric\n";
}
(bitwise is available in perl 5.022 and above, and enabled by default if you use 5.028; or above.)
Check out the CPAN module Regexp::Common. I think it does exactly what you need and handles all the edge cases (e.g. real numbers, scientific notation, etc). e.g.
use Regexp::Common;
if ($var =~ /$RE{num}{real}/) { print q{a number}; }
Usually number validation is done with regular expressions. This code will determine if something is numeric as well as check for undefined variables as to not throw warnings:
sub is_integer {
defined $_[0] && $_[0] =~ /^[+-]?\d+$/;
}
sub is_float {
defined $_[0] && $_[0] =~ /^[+-]?\d+(\.\d+)?$/;
}
Here's some reading material you should look at.
A simple (and maybe simplistic) answer to the question is the content of $x numeric is the following:
if ($x eq $x+0) { .... }
It does a textual comparison of the original $x with the $x converted to a numeric value.
Not perfect, but you can use a regex:
sub isnumber
{
shift =~ /^-?\d+\.?\d*$/;
}
A slightly more robust regex can be found in Regexp::Common.
It sounds like you want to know if Perl thinks a variable is numeric. Here's a function that traps that warning:
sub is_number{
my $n = shift;
my $ret = 1;
$SIG{"__WARN__"} = sub {$ret = 0};
eval { my $x = $n + 1 };
return $ret
}
Another option is to turn off the warning locally:
{
no warnings "numeric"; # Ignore "isn't numeric" warning
... # Use a variable that might not be numeric
}
Note that non-numeric variables will be silently converted to 0, which is probably what you wanted anyway.
rexep not perfect... this is:
use Try::Tiny;
sub is_numeric {
my ($x) = #_;
my $numeric = 1;
try {
use warnings FATAL => qw/numeric/;
0 + $x;
}
catch {
$numeric = 0;
};
return $numeric;
}
Try this:
If (($x !~ /\D/) && ($x ne "")) { ... }
I found this interesting though
if ( $value + 0 eq $value) {
# A number
push #args, $value;
} else {
# A string
push #args, "'$value'";
}
Personally I think that the way to go is to rely on Perl's internal context to make the solution bullet-proof. A good regexp could match all the valid numeric values and none of the non-numeric ones (or vice versa), but as there is a way of employing the same logic the interpreter is using it should be safer to rely on that directly.
As I tend to run my scripts with -w, I had to combine the idea of comparing the result of "value plus zero" to the original value with the no warnings based approach of #ysth:
do {
no warnings "numeric";
if ($x + 0 ne $x) { return "not numeric"; } else { return "numeric"; }
}
You can use Regular Expressions to determine if $foo is a number (or not).
Take a look here:
How do I determine whether a scalar is a number
There is a highly upvoted accepted answer around using a library function, but it includes the caveat that "inf" and "infinity" are accepted as numbers. I see some regex stuff for answers too, but they seem to have issues. I tried my hand at writing some regex that would work better (I'm sorry it's long)...
/^0$|^[+-]?[1-9][0-9]*$|^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$|^[+-]?[0-9]?\.[0-9]+$|^[+-]?[1-9][0-9]*\.[0-9]+$/
That's really 5 patterns separated by "or"...
Zero: ^0$
It's a kind of special case. It's the only integer that can start with 0.
Integers: ^[+-]?[1-9][0-9]*$
That makes sure the first digit is 1 to 9 and allows 0 to 9 for any of the following digits.
Scientific Numbers: ^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$
Uses the same idea that the base number can't start with zero since in proper scientific notation you start with the highest significant bit (meaning the first number won't be zero). However, my pattern allows for multiple digits left of the decimal point. That's incorrect, but I've already spent too much time on this... you could replace the [1-9][0-9]* with just [0-9] to force a single digit before the decimal point and allow for zeroes.
Short Float Numbers: ^[+-]?[0-9]?\.[0-9]+$
This is like a zero integer. It's special in that it can start with 0 if there is only one digit left of the decimal point. It does overlap the next pattern though...
Long Float Numbers: ^[+-]?[1-9][0-9]*\.[0-9]+$
This handles most float numbers and allows more than one digit left of the decimal point while still enforcing that the higher number of digits can't start with 0.
The simple function...
sub is_number {
my $testVal = shift;
return $testVal =~ /^0$|^[+-]?[1-9][0-9]*$|^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$|^[+-]?[0-9]?\.[0-9]+$|^[+-]?[1-9][0-9]*\.[0-9]+$/;
}
if ( defined $x && $x !~ m/\D/ ) {}
or
$x = 0 if ! $x;
if ( $x !~ m/\D/) {}
This is a slight variation on Veekay's answer but let me explain my reasoning for the change.
Performing a regex on an undefined value will cause error spew and will cause the code to exit in many if not most environments. Testing if the value is defined or setting a default case like i did in the alternative example before running the expression will, at a minimum, save your error log.