unable to give value to a scalar from command line in perl - 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.

Related

How to specify element in foreach loop to a variable?

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/.

Variable was returned to previous value in Perl

I'm just one-month experience in perl.
For perl-based program executing problem, the commend variable was returned to previous value.
What is the problem?
Here is the code.
1st();
2nd();
sub 1st {
$cmd = "cat asdf";
}
sub 2nd {
if ( $code =~ /aa/ ) {
my $cmd = "$reg $annotation";
out_log($cmd);
} else {
my $cmd = "$reg $annotation";
out_log($cmd);
}
out_log("$cmd");
open (Work,$cmd);
}
In this state, the $cmd was registered in if statement, but executing $cmd after if statement, the $cmd value was returned subroutine 1st's value.
Thanks for your advices.
You are mixing lexical and package variables. If your program had use strict and use warnings, this would be quite obvious.
If you do not declare a variable with my, Perl will assume it's a package variable. It will be visible from every part of your program (in the same namespace).
If you declare the variable with my, it will be lexical. That means that it only exists inside the scope it was created in.
my $foo = 1; #
#
if ($foo) { # #
my $bar = 2; # #
} # #
^ ^
| | scope that $foo exists in
| scope that $bar exists in
The same thing is happening here.
You are setting the package variable $::cmd (with :: being the main namespace) to "cat asdf" inside the 1st sub. You then call the 2nd sub, which will go into the else branch. In that scope, it will create a new lexical $cmd. It's only valid in that part of the program. It is then passed to out_log(), which probably prints it. Afterwards, you pass $::cmd with the "cat asdf" value to out_log(). At that point the new $cmd does not exist any more.
If you had use strict in your program, the program would not work at all, because the default package variable behavior is turned off in that case, so you have to define variables.
In fact you should not operate with package variables at all, but instead pass arguments to your functions.
In addition to that, there are a few other things that are not good practice in your program. You should use 3-argument open and a lexical filehandle, and also check the return value of open.
Names of functions cannot start with numbers, so 1st and 2nd are not valid names. It's better to name things after what they do or represent. That makes it easier to read your program later.
A full program might look like that.
use strict;
use warnings;
my ($code, $reg, $annotation); # these values come from somewhere...
run_cmd( compose_cmd(), $code, $reg, $annotation );
sub compose_cmd {
return "cat asdf";
}
sub run_cmd {
my ( $cmd, $code, $reg, $annotation ) = #_;
if ( $code =~ /aa/ ) {
my $cmd = "$reg $annotation";
out_log($cmd);
}
else {
my $cmd = "$reg $annotation";
out_log($cmd);
}
out_log("$cmd");
open my $fh, '<', $cmd or die $!;
# do stuff with $fh ...
}
sub out_log {
print #_;
}

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.

Passing variables through functions

When I pass a variable through a couple of subs, it always turns up empty. Why is this ?
sub Main {
my $myVariable = "Test string";
firstSub($myVariable);
}
sub firstSub {
my($myVariable) = #_;
my #array = `some command`;
secondSub(#array, $myVariable);
}
sub secondSub {
my(#array, $myVariable) = #_;
print $myVariable;
}
echo will be undef.
echo is not a valid Perl function. You're confusing shells with Perl here. Try "print" or "say" (the latter with Perl 5.10 and newer).
Also you cannot assign an array & a scalar variable to another array & scalar variable. Meaning this won't work because all of the elements of the right-hand side will be assigned to the array on left-hand side, and nothing will be assigned to the scalar: my (#array, $myVariable) = #_; Either swap the order of the elements my ($myVariable, #array) = #_; (also when calling the function) or use array references instead of full arrays.
Your code doesn't do anything because you have defined three subroutines, but you have never called them.
Just add Main(); to actually run your main sub.
Also, you need print instead of echo.
Also, the passing of variables is incorrect, as Moritz Bunkus explained.
When you call secondsub() the #array and the $myVariable is being sent as a list(a single element) and is been assigned to #array in the secondsub function. You can see both the #array and $myVariable values when you print #array in secondsub.
You have to pass the array as a reference and receive it as a scalar value in secondsub. The below code will work.
&Main();
sub Main {
my $myVariable = "Test string";
firstSub($myVariable);
}
sub firstSub {
my($myVariable) = #_;
my #array = `some command`;
secondSub(\#array,$myVariable);
}
sub secondSub {
my($ref,$myVariable) = #_;
print $myVariable;
}
Passing varibles:
my $txt = "this text for sample";
Function_Passing_varible($txt);
sub Function_Passing_varible{
my $text = shift;
print $text;
}
I think you like this answer......

Is there a better way to pass by reference in Perl?

I am doing pass-by-reference like this:
use strict;
use warnings;
sub repl {
local *line = \$_[0]; our $line;
$line = "new value";
}
sub doRepl {
my ($replFunc) = #_;
my $foo = "old value";
$replFunc->($foo);
print $foo; # prints "new value";
}
doRepl(\&repl);
Is there a cleaner way of doing it?
Prototypes don't work because I'm using a function reference (trust me that there's a good reason for using a function reference).
I also don't want to use $_[0] everywhere in repl because it's ugly.
Have you looked at Data::Alias? It lets you create lexically-scoped aliases with a clean syntax.
You can use it to create pass-by-reference semantics like this:
use strict;
use warnings;
use Data::Alias;
sub foo {
alias my ($arg) = #_;
$arg++;
}
my $count = 0;
foo($count);
print "$count\n";
The output is 1, indicating that the call to foo modified its argument.
There are a couple of ways to do this. Explicitly pass a scalar ref to $foo, or take advantage of Perl's built-in pass by reference semantics.
Explicit reference:
my $foo = "old value";
doRepl( \&repl, \$foo );
print $foo; # prints "new value";
sub repl {
my $line = shift;
$$line = "new value";
}
sub doRepl {
my ($replFunc, $foo) = #_;
$replFunc->($foo);
}
Pass by reference:
my $foo = "old value";
doRepl( \&repl, $foo );
print $foo; # prints "new value";
sub repl {
$_[0] = "new value";
}
sub doRepl {
my $replFunc = shift;
$replFunc->(#_);
}
Even fancier pass by reference:
my $foo = "old value";
doRepl( \&repl, $foo );
print $foo; # prints "new value";
sub repl {
$_[0] = "new value";
}
sub doRepl {
my $replFunc = shift;
&$replFunc;
}
The first one use normal perl hard references to do the job.
The first pass by ref method uses the fact that Perl passes arguments to all functions as references. The elements of #_ are actually aliases to the values in the argument list when the subroutine is called. By altering $_[0] in foo(), you actually alter the first argument to foo().
The second pass by ref method use the fact that a sub called with an & sigil and no parens gets the #_ array of its caller. Otherwise it is identical.
Update: I just noticed you desire to avoid $_[0]. You can do this in repl if you want:
sub repl {
for my $line( $_[0] ) {
$line = 'new value';
}
}
sub repl {
my $line = \$_[0]; # or: my $line = \shift
$$line = "new value";
}
I don't think there is anything wrong with using local to create the alias in this case.
Dynamic scope is of course a powerful feature, but so long as you are aware of the side effects (new value is visible in functions called from its scope, if a lexical of the same name is in scope, it can't be localized, ...) then it is a useful addition to the already overflowing Perl toolbox.
The main reason for the warnings in the Perl docs about local are to keep people from inadvertently using it instead of my and to ease the transition from perl4. But there are definitely times when local is useful, and this is one.
Using for to create your alias is also an option, but I find the explicit syntax with local clearer in its intent. It is also a bit faster if performance is a concern.