Perl, using variable from within While loop outside of the loop? - perl

This seems really simple but it is giving me a hard time figuring it out as I'm new to perl.. I've been looking through a lot of documentation now about loops and I am still stumped by this... I have a sub that contains a while loop and I want to use a variable value from within the loop outside of the loop (after the loop has run), however when I try to print out the variable, or return it out of the sub, it doesn't work, only when I print the variable from within the loop does it work.. I would appreciate any advice as to what I'm doing wrong.
Doesn't work (doesn't print $test ):
sub testthis {
$i = 1;
while ($i <= 2) {
my $test = 'its working' ;
$i++ ;
}
print $test ;
}
&testthis ;
Works, prints $test:
sub testthis {
$i = 1;
while ($i <= 2) {
my $test = 'its working' ;
$i++ ;
print $test ;
}
}
&testthis ;

You declare variable test inside the loop, so it scope is the loop, as soon as you leave the loop the variable is not longer declared.
Add my $test; just between $i=1 and while(..) and it will work. The scope will now be the entire sub instead of only the loop

Place my $test before the while loop. Note that it will only contain the last value that is assigned in the while loop. Is that what you are after?
// will print "it's working" when 'the loop is hit at least once,
// otherwise it'll print "it's not working"
sub testthis {
$i = 1;
my $test = "it's not working";
while ($i <= 2) {
$test = "it's working";
$i++ ;
}
print $test ;
}

you can try this one:
sub testthis {
my $test
$i = 1;
while ($i <= 2) {
$test = 'its working' ;
$i++ ;
print $test ;
}
}
&testthis ;
Note: whenever write perl code, its better to add use strict; and use warning in the beginning of the code.

Related

The statements inside the label executes even if the condition fails.Can anyone explain what I'm doing here wrong?

my $a =10;
my $b =200;
my $c,$d;
goto UP if ($a > 20);
$d = $c + $b;
print "$d\n";
UP:
$c = $b -$a;
print "$c\n";
The above statements inside the label executes even if the condition fails.
It seems like you are mixing up how the labels and goto work. In fact, goto should never be used for flow control. It's really confusing.
This is how your code runs at the moment:
As you can see, the last two statements (those are after your UP label) are always executed. Perl will check the condition, and if it is true, skip ahead. If the condition is false, it runs the two statements following immediately, and then runs the label and the rest.
Labels don't make subroutines in Perl. They just give a line a name. The line of code is still executed normally.
If you want to do one or the other instead, you need an if-else construct. That's done like this in Perl.
my $a = 10;
my $b = 200;
my ( $c, $d );
if ($a > 20) {
$c = $b -$a;
print "$c\n";
} else {
$d = $c + $b;
print "$d\n";
}
Since you seem to insist on goto, you can make that work, but you need to tell it to stop execution.
my $a =10;
my $b =200;
my $c,$d;
goto UP if ($a > 20);
$d = $c + $b;
print "$d\n";
exit; # <------------- this stops execution and ends the program
UP:
$c = $b -$a;
print "$c\n";
Of course your code won't do much, because both $c and $d are undef.
You should really turn on use strict and use warnings, and fix the problems both of these pragmata will show you.
Also note that $a and $b are reserved variables to be used inside sort.
The code you posted is equivalent to the following:[1]
A: my $a = 10;
B: my $b = 200;
C: my ($c, $d);
D: goto G if $a > 20;
E: $d = $a + $b;
F: print "$d\n";
G: $c = $b - $a;
H: print "$c\n";
As this better demonstrates, code execution doesn't stop once a label is encountered.
To emulate an if-then-else structure, you'd use
my $a = 10;
my $b = 200;
$a > 20 and goto ELSE;
my $d = $a + $b;
print "$d\n";
goto END;
ELSE:
my $c = $b - $a;
print "$c\n";
END:
You probably know this, but the following is a lot clearer and provides better scoping:
my $a = 10;
my $b = 200;
if ($a <= 20) {
my $d = $a + $b;
print "$d\n";
} else {
my $c = $b - $a;
print "$c\n";
}
After a couple of off-topic fixes

Sleep function not working inside for loop using Perl Script

My code is:
#!/usr/local/bin/perl
use Time::Piece;
use Time::HiRes;
use strict;
use warnings 'all';
my $i = 1;
my $starttime = localtime->strftime('%Y%m%d%H%M');
open my $file, '>', 'order.properties' or die $!;
for ($i = 1; $i <= 10; $i++){
print $file "Start_time_$i = $starttime\n";
sleep (120);
}
close $file;
In the above code I am creating an order.properties file and writing a variable called Starttime and assigning date and time in a format of YYYYMMDDHH24MM and iterating the variable for 10 time with sleep time 2 mins, but sleep is not working and after adding sleep function to the Script, it's just creating a file not writing anything into it.
For each iteration of for loop I need 2 mins of sleep like:
Start_time_1 = 201812141350
Start_time_2 = 201812141352
The output should be like above.
You set $starttime outside of the loop and never change it. Therefore it always has the same value. If you want it to change in the loop, then you need to change it in the loop.
for ($i = 1; $i <= 10; $i++){
my $starttime = localtime->strftime('%Y%m%d%H%M');
print $file "Start_time_$i = $starttime\n";
sleep (120);
}
Of course, at that point, you have to wonder if there's any good reason to have the variable at all.
for ($i = 1; $i <= 10; $i++){
print $file "Start_time_$i = ", localtime->strftime('%Y%m%d%H%M'), "\n";
sleep (120);
}
And, please make your maintenance programmer's life easier by using a foreach loop there.
foreach my $i (1 .. 10) {
print $file "Start_time_$i = ", localtime->strftime('%Y%m%d%H%M'), "\n";
sleep (120);
}

Perl file list compare

I need to iterate through and compare all files passed in as command line arguments in a perl script.
For example:
./script f1.txt f2.txt f3.txt
I'll need to compare
f1 & f2,
f1 & f3,
f2 & f3,
So that all files are compared to each other in some way, and not repeated.
I can do the internal 'comparing' of the files just fine, it's the way to get the files paired up which is the problem for me.
Any help to discover a way for this would be muchly appreciated!
You just want to compare every argument against every argument past itself. The ones before it would have been compared already, so you just have to look beyond. Something like this:
for (my $i = 0; $i < #ARGV; ++$i)
{
for (my $j = $i + 1; $j < #ARGV; ++$j)
{
my $f1 = $ARGV[$i];
my $f2 = $ARGV[$j];
say "Comparing $f1 to $f2";
}
}
Assuming that comparing "p" and "q" is the same as comparing "q" and "p", then you can do something like this. Here, #filelist is a changing list of files that haven't yet been the left-hand side of the compare. In each iteration of the outer loop, we take one element out of that, and compare it against all the rest.
my #filelist = #ARGV;
while (#filelist) {
my $p = shift #filelist;
foreach my $q (#filelist) {
compare($p, $q);
}
}
You could do the same thing with indices instead. Here, $p counts from 0 to the number of files you have, and $q starts counting from $p.
foreach my $p (0..$#ARGV) {
foreach my $q ($p+1..$#ARGV) {
compare($ARGV[$p], $ARGV[$q]);
}
}
If comparing "p" and "q" is different than comparing "q" and "p", then it gets a bit easier:
foreach my $p (#ARGV) {
foreach my $q (#ARGV) {
compare($p, $q) unless $p eq $q;
}
}

Display folders using perl script

I have the following code and my problem is that I cannot modify it in order to use the $file3 outside the for function
for ($i = 0; $i < scalar(#temp); $i++){
$path7 = 'path_to'.#temp[$i];
foreach $path ($path7){
opendir my ($dh3), $path7 or die $!;
while ( my $file3 = readdir $dh3 ) {
next if $file3 eq '.' or $file3 eq '..';
next unless -d catfile($path7, $file3);
print "$file3\n";
}
closedir ($dh3);
}
}
Your $file3 is lexical to the while loop because you declared it with my. If you want it to be available outside, declare it in a larger scope, i.e. outside the for.
my $file3; # here!
for ( ...) {
# ...
# ...
######### no my below
while ( $file3 = readdir $dh3 ) {
# ...
}
# ...
}
Remember that in Perl it's a good practice to declare variables in the smallest scope necessary.
Also note that outside the while loop it will start out being undef and after being done processing the while for the first time ($i is 0, $path is the value of $path7), $file3 will keep the value it had in the last round of the while loop until the next time the while loop starts. That is never, because your foreach's list only has one element, as $path7 is a scalar and not an array. In fact, there is no need for that foreach loop at all. Just use $path7 directly.
Confused with my explanation because of the variable names? Me too. Always pick meaningfull variable names, don't just append numbers. That makes it very hard to maintain. :)

What am I not getting about foreach loops?

It was always my understanding that
foreach (#arr)
{
....
}
and
for(my $i=0; $i<#arr; $i++)
{
.....
}
were functionally equivalent.
However, in all of my code, whenever I use a foreach loop I run into problems that get fixed when I change to a for loop. It always has to do with comparing the values of two things, usually with nested loops.
Here is an example:
for(my $i=0; $i<#files; $i++)
{
my $sel;
foreach (#selected)
{
if(files[$i] eq selected[$_])
{
$selected='selected';
}
}
<option value=$Files[$i] $sel>$files[$i]</option>
}
The above code falls between select tags in a cgi program.
Basically I am editing the contents of a select box according to user specifications.
But after they add or delete choices I want the choices that were origionally selected to remain selected.
The above code is supposed to accomplish this when reassembling the select on the next form. However, with the foreach version it only gets the first choice that's selected and skips the rest. If I switch it to a 3 part for loop, without changing anything else, it will work as intended.
This is only a recent example, so clearly I am missing something here, can anyone help me out?
Let's assume that #files is a list of filenames.
In the following code, $i is the array index (i.e. it's an integer):
for (my $i=0; $i<#files; $i++) { ... }
In the following code, $i is set to each array item in turn (i.e. it's a filename):
foreach my $i (#files) { ... }
So for example:
use strict;
use warnings;
my #files = (
'foo.txt',
'bar.txt',
'baz.txt',
);
print "for...\n";
for (my $i=0; $i<#files; $i++) {
print "\$i is $i.\n";
}
print "foreach...\n";
foreach my $i (#files) {
print "\$i is $i.\n";
}
Produces the following output:
for...
$i is 0.
$i is 1.
$i is 2.
foreach...
$i is foo.txt.
$i is bar.txt.
$i is baz.txt.
foreach loops are generally preferred for looping through arrays to avoid accidental off-by-one errors caused by things like for (my $i=1;...;...) or for (my $i=0;$i<=#arr;...).
That said, for and foreach are actually implemented as synonyms in Perl, so the following script produces identical output to my previous example:
use strict;
use warnings;
my #files = (
'foo.txt',
'bar.txt',
'baz.txt',
);
print "for...\n";
foreach (my $i=0; $i<#files; $i++) {
print "\$i is $i.\n";
}
print "foreach...\n";
for my $i (#files) {
print "\$i is $i.\n";
}
It it simply customary to refer to the second type of loop as a foreach loop, even if the source code uses the keyword for to perform the loop (as has become quite common).