How to specify element in foreach loop to a variable? - perl

I use below Perl code, and want to add specific $i in foreach into variable $targetBank and $targetEntry.
For 1_Target Bank & 1_Target Entry they are ok to print out.
But for 2_Target Bank & 2_Target Entry they are not working.
It seems the $targetBank and $targetEntry variable refresh every time in the foreach loop.
How should I modify my code?
my #SPLIT =split /\./, $longSentense;
foreach my $i (#SPLIT)
{
if($i =~ /u_b.*/)
{
my $targetBank=$i;
print "1_Target Bank= $targetBank\n";
}
elsif ($i =~ /g_tq.*/)
{
my $targetEntry=$i;
print "1_Target Entry= $targetEntry\n";
}
}
print "2_Target Bank= $targetBank\n";
print "2_Target Entry= $targetEntry\n";

First, always add use strict; use warnings; at the beginning of your scripts (more or that later).
A variable is only visible in the scope it was defined in. For instance, if you do:
{
my $var = 42;
print "1: $var\n"; # prints "1: 42"
}
print "2: $var\n"; # ERROR !!!
$var is visible inside the { ... } block, but not after. With use strict, the print "2: $var\n"; will not compile, because $var is not declared in this scope. Without use strict, this statement will work, but $var will be undefined.
To get this example to work, you need to declare $var in the same scope as print "2: $var\n":
my $var;
{
$var = 42;
print "1: $var\n"; # prints "1: 42"
}
print "2: $var\n"; # prints "2: 42", as expected.
Note how I wrote $var = 42 rather than my $var = 42: the later would declare a new variable (in this scope only), which would "shadow" the previous declaration, and, when exiting the scope (at }), the $var that would be visible would be the one declare before the block, which would still be undefined.
Also, the word "scope" is maybe a bit confusing. You can (mostly) think of a scope as "everything inside curly braces + the global scope": if () { new scope here }, for (...) { new scope here }, sub { new scope here }, and, everything that is not inside a block is in the global scope. Small subtlety: when you write if (my $var = ...) { ... } or for my $var (...) { ... }, it introduces 2 scopes: one with $var, and one inside the { ... } (and they are both closed at the end of the if/for). See this small tutorial about scopes.
Thus, your code should be:
use strict; # never omit
use warnings; # those 2 lines
my #SPLIT =split /\./, $longSentense;
my ($targetBank, $targetEntry); # Declaring your variables
foreach my $i (#SPLIT)
{
if($i =~ /u_b/)
{
$targetBank=$i;
print "1_Target Bank= $targetBank\n";
}
elsif ($i =~ /g_tq/)
{
$targetEntry=$i;
print "1_Target Entry= $targetEntry\n";
}
}
print "2_Target Bank= $targetBank\n";
print "2_Target Entry= $targetEntry\n";
Note that each time $targetBank=$i; is executed, the previous value of $targetBank is lost. If the condition if($i =~ /u_b/) is true only once in your loop, then this is not an issue. Otherwise, you might want to use an array instead of a scalar to store multiple values.
Also, as pointed out by #TLP, /u_b.*/ and /g_tq.*/ can be simplified to /u_b/ and /g_tq/.

Related

unable to give value to a scalar from command line in perl

I am trying the following perl code:
use strict;
use warnings;
use Scalar::Util ("looks_like_number");
my $color;
if (undef $color)
{
my $color = $ARGV[0];
}
print "$0\n";
print "$ARGV[0]\n";
my #colors = ("blue", "yellow", "brown", "white");
print "Please select a num:\n";
foreach my $i (0..$#colors)
{
my $j = $i+1;
print " $j $colors[$i]\n";
}
my $num = <STDIN>;
chomp($num);
if (looks_like_number($num) and defined $colors[$num-1])
{
$color = $colors[$num-1];
}
else
{
print "Bad Selection\n";
}
print "selected color is $color\n";
I want to select any number for the corresponding color choice or I should be able to provide a value of color by $color variable through command line, I am trying to run it in windows cmd using ' perl [C:/scriptname.pl] [color] ' but its not taking the argument but when I am printing ARGV[0] it is showing the argument being passed correctly. so what is the issue with my 'if (undef ARGV[0])' statement that its not getting executed.
You are declaring the variable $color twice:
my $color;
if (undef $color)
{
my $color = $ARGV[0];
}
The second my $color will create a second binding of the name $color and the value you assign to it and that binding will not be visibile outside of the scope, the enclosing curly brackets.
The expression
if (undef $color)
does not do what you intend it to do. undef will always set a value to undef. You want to use defined instead.
After applying those two changes, the code could look like:
my $color;
if (! defined $color)
{
$color = $ARGV[0];
}
my $color;
if (undef $color)
{
my $color = $ARGV[0];
}
This looks rather strange. In line 1 you declare a scalar variable called $color. In line 2, you call undef() on that variable which replaces the contents of $color with undef() (that's unnecessary as a newly-declared Perl scalar will always contain undef()). This expression will return undef, which is false, so the code in your if block is never executed.
In line 4, you declare a new variable, also called $color and set that to $ARGV[0]. There are two problems with this. Firstly, that line will never be executed for the reasons explained in the previous paragraph. And, secondly, your new $color variable will cease to exist once you leave the block, so you would never see the effect of this change.
I think the main problem here is that you have confused undef() with defined(). undef() will always give a variable an undefined values, but I suspect you wanted defined() which tells you whether or not a variable contains a defined value.
What you probably wanted was:
my $color;
if (!defined $color) {
$color = $ARGV[0];
}
Which can be written as:
my $color //= $ARGV[0];
But it's still slightly confusing as it's not clear why you think $color could ever be defined immediately after being declared.

Beginner - Subroutine confusion

I'm a beginner and confused about what's happening inside this Perl subroutine.
I'm using only global variables to simplify things, but it's still not working.
I'm simply trying to print a file's read, write and executable attributes using the file test operators with IF statements.
Can anyone point out the problem for me?
Louie
sub getfileattributes {
if (-r $file) {
$attributes[0] = "readable";
} else { $attributes[0] = "not readable"; }
if (-w _) {
$attributes[1] = "writable";
} else { $attributes[1] = "not writable"; }
if (-x _) {
$attributes[2] = "executable";
} else { $attributes[2] = "not executable"; }
}
my #attributes;
my $file;
foreach $file (#ARGV) {
&getfileattributes;
printf "The file $file is %s, %s and %s\n", #attributes;
}
Using global variables is usually quite bad and points to a design error. In this case, the error seems to be that you don't know how to pass arguments to a sub.
Here is the pattern in Perl:
sub I_take_arguments {
# all my arguments are in #_ array
my ($firstarg, $secondarg, #rest) = #_;
say "1st argument: $firstarg";
say "2nd argument: " .($firstarg+1). " (incremented)";
say "The rest is: [#rest]";
}
Subs are invoked like
I_take_arguments(1, 2, "three", 4);
(Do not invoke them as &nameOfTheSub, this makes use of very special behaviour you don't usually want.)
This would print
1st argument: 1
2nd argument: 3
The rest is: [three 4]
Subroutines can return values, either with the return statement or as the value of the last statement that is executed. These subs are equivalent:
sub foo {return "return value"}
sub bar {"return value"}
I would write your getfileattributes as
sub getFileAttributes {
my ($name) = #_;
return
-r $name ? "readable" : "not readable",
-w $name ? "writable" : "not writable",
-x $name ? "executable" : "not executable";
}
What is happening here? I take an argument $name and then return a list of values. The return keyword could be omitted. The return takes a list of values and does not require parens, so I leave them out. The TEST ? TRUE-STATEMENT : FALSE-STATEMENT operator is known from other languages.
Then, in your loop, the sub would be invoked like
for my $filename (#ARGV) {
my ($r, $w, $x) = getFileAttributes($filename);
say "The file $filename is $r, $w and $x";
}
or
foreach my $file (#ARGV) {
my #attributes = getFileAttributes($file);
printf "The file $file is %s, %s and %s\n", #attributes;
}
Notes:
say is like print, but adds a newline at the end. To use it, you have to have a Perl > 5.10 and you should use 5.010 or whatever version or use feature qw(say).
always use strict; use warnings; unless you know better for sure.
Often, you can write programs without assigning to a variable twice (Single assignment form). This can make reasoning about control flow much easier. This is why global variables (but not global constants) are bad.
You are not actually using global varaibles. My scopes the variables them local to the main routine, so when you call the subroutine, $file and #attributes are scoped to the subroutine, not to the main routine.
Change my to our for $file and #attributes to make the variables global and available to the subroutine.
You can check this for yourself by using the -d argument for perl to run it in the debugger and check the values of the items.

Creating a dynamic Perl function which uses a variable's value at time of function declaration

I'd like to create a dynamic function that uses (evaluates?) the value of a variable at the time the function is declared.
The example below requires $var to exist as a global variable so it can be used when the function is invoked:
my $var = 'something';
someFunction(sub { return $_[0] eq $var; });
but I'm guessing there is some way to create the dynamic function so it is declared like this:
someFunction(sub { return $_[0] eq 'something'; });
How can I do that!? :)
A little sloppy, but it works:
#!/usr/bin/env perl
use warnings;
use strict;
my $var = 'something';
my $f1 = sub { my $v = $_[0]; return sub { return $_[0] eq $v } };
my $f2 = $f1->($var);
$var = 'other thing';
print $f2->('something');
With lambda, all things are possible.
How about capturing a local copy of it?
someFunction( do { my $v = $var; sub { $_[0] eq $v } } );
That way, even if $var is later modified, the anonymous sub is still using its local copy of it from with the original value.
What's wrong with an old, simple, straight closure?
sub genf { my $v = shift; sub { shift eq $v } }
my $f = genf('something'); # Or genf($var)
print &$f('something');
print &$f('another thing');
Like the others, I think that a closure is fine for this purpose. I wouldn't even be surprised if the compiler can optimize it down to what you expect, though I don't have the guru-ness to prove it.
Still, I can attempt what you asked, though I don't recommend it.
my $var = 'something';
my $sub = eval 'sub { return $_[0] eq \'' . $var . '\'}';
someFunction( $sub );
You build up the code reference as strings, using the value of $var and then when you eval it, it is compiled to Perl code. Notice that you have to include extra quotes since by the time the code is evaluated, the contents of $var will be a bare string.
Again though, this isn't recommended. Why? Because its dangerous, especially if the content of $var comes from the outside world.

Why do I get a duplicate declaration in same scope warning in an if/elsif tree?

Why does the following code warn? $match is scoped to the if block and not the containing while block.
use strict;
use warnings;
use 5.012;
use IO::All;
my $file = io($ARGV[0])->tie;
my $regex = qr//;
while (my $line = <$file>) {
if (my ($match) = $line =~ $regex) {
...
}
elsif (my ($match) = $line =~ $regex) {
...
}
say $match;
}
C:\>perl testwarn.pl test.log
"my" variable $match masks earlier declaration in same scope at testwarn.pl line 15.
Global symbol "$match" requires explicit package name at testwarn.pl line 18.
Execution of testwarn.pl aborted due to compilation errors.
As expected, it complains that $match is not defined at line 18, but it also complains about the redeclaration of $match in the if blocks. Version is slightly out of date but not horribly so; and it's the most recent Strawberry version:
This is perl 5, version 12, subversion 3 (v5.12.3) built for MSWin32-x86-multi-thread
The scope of the first $match declaration is the entire if-elsif-else block. That lets you do things like this:
if ( (my $foo = some_value()) < some_other_value() ) {
do_something();
} elsif ($foo < yet_another_value()) { # same $foo as in the if() above
do_something_else();
} else {
warn "\$foo was $foo\n"; # same $foo
} # now $foo goes out of scope
print $foo; # error under 'use strict' => $foo is now out of scope
If we were to declare my $foo anywhere else in this block, including in the elsif (...) clause, that would be a duplicate declaration in the same scope, and we'd get a warning message.
Because a variable declared in the an if test is visible to the whole if/elsif/else statement:
#!/usr/bin/perl
use strict;
use warnings;
sub test {
if (my $arg = shift) {
print "$arg is true\n";
} else {
print defined $arg ? $arg : "undef", " is false\n";
}
}
test undef;
test 0;
test 0.0;
test "0.0";
test 1
this block
if (my ($match) = $line =~ $regex) {
...
elsif (my ($match) = $line =~ $regex) {
scopes $match to the if/else block and not to the while loop. And yet you are referencing it from the while block.
move it here
my $match;
if ($match = ($line =~ $regex)) {
if creates a lexical scope for the entire statement, and another lexical scope for each block (curlies).
In the following, each letter represents the span of a lexical scope:
A
if (B) {
C
} elsif (B) {
D
} else {
E
}
A
Defining two variables with the same name in the same lexical scope results in the «"my" variable %s masks earlier declaration in same scope» warning.
Using a variable that wasn't declared in a lexical scope or an ancestral lexical scope results in the «Global symbol "%s" requires explicit package name» error.
Lexical scopes "C", "D" and "E" are children of lexical scope "B".
Lexical scope "B" is a child of lexical scope "A".
This warning can be suppressed by including
no warnings qw/misc/;
in the containing block. When doing so, it will not warn about the redeclaration but will still die when attempting to access the variable outside of the if blocks.

How can I list all variables that are in a given scope?

I know I can list all of the package and lexcial variables in a given scope using Padwalker's peek_our and peek_my, but how can I get the names and values of all of the global variables like $" and $/?
#!/usr/bin/perl
use strict;
use warnings;
use PadWalker qw/peek_our peek_my/;
use Data::Dumper;
our $foo = 1;
our $bar = 2;
{
my $foo = 3;
print Dumper in_scope_variables();
}
print Dumper in_scope_variables();
sub in_scope_variables {
my %in_scope = %{peek_our(1)};
my $lexical = peek_my(1);
#lexicals hide package variables
while (my ($var, $ref) = each %$lexical) {
$in_scope{$var} = $ref;
}
##############################################
#FIXME: need to add globals to %in_scope here#
##############################################
return \%in_scope;
}
You can access the symbol table, check out p. 293 of "Programming Perl"
Also look at "Mastering Perl: http://www252.pair.com/comdog/mastering_perl/
Specifically: http://www252.pair.com/comdog/mastering_perl/Chapters/08.symbol_tables.html
Those variables you are looking for will be under the main namespace
A quick Google search gave me:
{
no strict 'refs';
foreach my $entry ( keys %main:: )
{
print "$entry\n";
}
}
You can also do
*sym = $main::{"/"}
and likewise for other values
If you want to find the type of the symbol you can do (from mastering perl):
foreach my $entry ( keys %main:: )
{
print "-" x 30, "Name: $entry\n";
print "\tscalar is defined\n" if defined ${$entry};
print "\tarray is defined\n" if defined #{$entry};
print "\thash is defined\n" if defined %{$entry};
print "\tsub is defined\n" if defined &{$entry};
}
And that does it. Thanks to MGoDave and kbosak for providing the answer in front of my face that I was too stupid to see (I looked in %main:: to start with, but missed that they didn't have their sigils). Here is the complete code:
#!/usr/bin/perl
use strict;
use warnings;
use PadWalker qw/peek_our peek_my/;
use Data::Dumper;
our $foo = 1;
our $bar = 2;
{
my $foo = 3;
print Dumper in_scope_variables();
}
print Dumper in_scope_variables();
sub in_scope_variables {
my %in_scope = %{peek_our(1)};
my $lexical = peek_my(1);
for my $name (keys %main::) {
my $glob = $main::{$name};
if (defined ${$glob}) {
$in_scope{'$' . $name} = ${$glob};
}
if (defined #{$glob}) {
$in_scope{'#' . $name} = [#{$glob}];
}
if (defined %{$glob}) {
$in_scope{'%' . $name} = {%{$glob}};
}
}
#lexicals hide package variables
while (my ($var, $ref) = each %$lexical) {
$in_scope{$var} = $ref;
}
return \%in_scope;
}
You can do something like the following to check the symbol table of the main package:
{
no strict 'refs';
for my $var (keys %{'main::'}) {
print "$var\n";
}
}
Thanks, Chas, very useful code.
As a note for future users of your code with perl > 5.12:
I was using it in in my pdl2 .perldlrc to find out lexical variables (like the 'y' command in the debugger) and I had this warning:
load_rcfile: loading
/homes/pmg/.perldlrc defined(%hash) is deprecated at (eval 254) line 36.
(Maybe you should just omit the defined()?)
From perldoc -f defined
Use of defined on aggregates (hashes
and arrays) is deprecated. It used to
report whether memory for that
aggregate had ever been allocated.
This behavior may disappear in future
versions of Perl. You should instead
use a simple test for size:
> if (#an_array) { print "has array elements\n" }
> if (%a_hash) { print "has hash members\n" }
What I don't understand is why it only complained with the defined hash and not also with the array?