Perl while loop only looping once within a for loop - perl

I'm new to perl and struggling with this. How can I make the following code iterate asterisk_Output each run of the for loop? At the moment it completes the while loop on the first iteration of the for loop but not on subsequent ones.
open(asterisk_Output, "/usr/sbin/asterisk -rx \"sip show registry\"|") or die $!;
foreach (#monitor_trunks){
while (my $line = <asterisk_Output>) {
#Perform some action... Such as comparing each line.
}
}
The only way I have got it working is by putting the top line within the for loop, but this is un necessary and make multiple calls to the external command.

At the end of your first loop, the file pointer is at the end of the file. You have to bring it back to beginning if you need another round.
You can try either to rewind the file:
seek(asterisk_Output,0,1);
or (if your logic allows it) to change foreach and while (so that you only read it once):
while (my $line = <asterisk_Output>){
foreach (#monitor_trunks) {
#Perform some action... Such as comparing each line.
}
}
The third option would be to read the whole file into an array and use it as an input for your loop:
#array = <asterisk_Output>;
foreach (#monitor_trunks){
for my $line (#array) {
#Perform some action...
}
}

Related

How to force Perl scripts quit when it is running in loops?

I use Perl to analyze my research data (multiple large files which may be edited or modified by users while program is running).
In my program, there are scripts to check whether the file is complete or not before it analyze the data in one of files. This check is processed in multiple loops. If I simply use "exit", it only exit a loop. How can I force the scripts to quit and generate an error message for user before it quit? In my program, there is a defined variable which be output to a log file at the end of the program. I do NOT want to use GOTO command. Any further information is highly appreciated.
......
foreach $dir (#dirs)
{
...
$file="$dir$filename";
$file_size=`wc -l $file`;
$line=`grep -n TIMESTEP $file`;
#read the first line no of each frame in a data file
#values=split(/\r?\n/,$line);
$loop_i=0;
$tmp=0; #save line no for the first frame
foreach $sub_line (#values)
{
#sub_values=split(/:/,$sub_line);
$line_no[$loop_i]=$sub_values[0];
#check the line number in each frame same or not, if not quit
if($loop_i==1){$tmp=$line_no[$loop_i]-$line_no[$loop_i-1];}
elsif($loop_i>1)
{ $_=$line_no[$loop_i]-$line_no[$loop_i-1];
if($_ <> $tmp)
{$flag=0; $err_message="$err_message; incomplete data (each frame has different line number)";
exit; #cannot quit the whole program
}
}
else{;}
$loop_i++;
}#end foreach $sub_line (#values)
.....
}#end foreach $dir (#dirs)
....
I think what you want are loop controls. You can use next, last, or redo to break out of a loop early, stop a loop completely, or process the same iteration again. With nested loops you can use a label to specify which loop you want to control:
DIR: foreach my $dir ( ... ) {
...
LINE: foreach my $line ( ... ) {
next LINE if $skip_line;
last LINE if ...;
next DIR if ...;
}
}

Perl while loops not working

I'm quite new to perl and apologies if this has already been answered in a previous discussion. I have a script that needs to use the declared variables outside the loops, but only one loop is working, even though I have declared the variables outside of the loop, the code is:
my $sample;
open(IN, 'ls /*_R1_*.gz |');
while (my $sample = <IN>) {
chomp $sample;
print "sample = $sample\n";
my $fastq1="${sample}"; #need to use fastq1 later on hence it's declared here
my $sample2;
open(IN, 'ls /*_R2_*.gz |');
while (my $sample2 = <IN>) {
chomp $sample2;
print "sample2 = $sample2\n";
my $fastq2="${sample2}"; #need to use fastq2 later on hence it's declared here
}
}
Sample2 works but sample1 does not, only the first sample is output and then the loop goes onto sample2, the output is:
sample =/sample1_R1_001.fastq.gz
sample2 =/sample1_R2_001.fastq.gz
sample2 =/sample2_R2_001.fastq.gz
sample2 =/sample3_R2_001.fastq.gz
etc..
Can anyone figure this out?
Thanks
From your comments, I assume that your problem is probably that you declare $fastq1 and $fastq2 inside the loop. That means they will be out of scope outside the loops, and not accessible. You need something like:
my ($fastq1, $fastq2);
while ( ... ) {
....
$fastq1 = $sample;
}
Note that this will only save the last value in the loop of that variable. The others will of course be overwritten each loop iteration. If you have more values to save, use an array or hash.
Some other notes on your code.
You should always use
use strict;
use warnings;
Not doing so is a very bad idea, as it will only hide the errors and warnings, not solve them.
my $sample;
You declare this variable twice.
open(IN, 'ls /*_R1_*.gz |');
This is just bad on all possible levels:
System calls are always the least desirable option, unless no alternatives exist
Perl has many ways of reading file names
Parsing the output of ls is fragile and not portable
Piping the result of the system command through open is compounding the other flaws with this approach.
Recommended solution: Use either opendir + readdir or glob:
for my $files (</*_R1_*.gz>) { ... }
# or
opendir my $dh, "/" or die $!;
while (my file = readdir $dh) {
next unless $file =~ /_R1_.*\.gz$/;
...
}
my $fastq1 = "${sample}";
You do not need to quote a variable. Nor use support curly braces.
When declaring the variable with my inside a loop, it only retains its value that single loop iteration. Since you never use this variable, I assume you meant to use it outside the loop. But it will be out of scope there.
This can be written
my $fastq1 = $sample;
But you probably want to declare those variables outside your while loops, or they will be out of scope there. You should know that this will only save the last value for these variables, of course.
Also, as Rohit says, your loops are nested, which I assume is not what you wanted. This is most likely because you do not use a proper text editor to write your code, so your indentation is all messed up, and it is hard to see where one loop ends. Follow Rohit's advice there.
You are closing the first while loop after the end of 2nd while loop. Because of that, your 2nd while loop become a part of your 1st while loop, wherein, you are re-assigning the file handler - IN to a different file. And since you are exhausting it in the inner while loop, your outer while loop never run again.
You should close the brace before starting the next while:
while(my $sample = <IN>){
chomp $sample;
print "sample = $sample\n";
my $fastq1="${sample}";
} # You need this
my $sample2;
open(IN, 'ls /data_n2/vmistry/Fluidigm_Exome/300bp_fastq/*_R2_*.gz |');
while(my $sample2 = <IN>){
chomp $sample2;
print "sample2 = $sample2\n";
my $fastq2="${sample2}";
}
# } # Remove this

Count records in Perl

Is there a built-in Perl variable that keeps track of how many records have been read in a while loop?
For example, suppose I do this:
my $count;
while (<>) {
$count++;
}
print $count;
Is there a way to do this without defining $count? That is, is there already some variable that contains this information?
$. will tell you the current line number for the current file being read.
Note that the variable resets on a close() call to the filehandle, so if the old file handle isn't closed when you start reading from a new one then the variable will keep incrementing even across files. However, if the filehandle is closed you'll have it reset to 0. For example, the code in your example and this code will continuously count across files being read:
foreach my $arg (#ARGV) {
open(I, $arg);
while(<I>) {
print $.,"\n";
}
}
But if you close the filehandle at any point before the next open call:
foreach my $arg (#ARGV) {
open(I, $arg);
while(<I>) {
print $.,"\n";
}
close(I); # NEW LINE
}
then it'll reset $. to zero again and you'll get unique counts per file.
There is no automatic loop counter in Perl. There are counters to count the current line number in a filehandle (see Wes Hardaker).
The loopcounter would be very complex (how to handle a loop inside a loop?).
So, back to the old $count++ :)
You can use a simple command line script, too:
perl -ne 'if (eof) {printf "%6d %s\n",$.,$ARGV;close #ARGV}' file1 file2 file3
10 file1
13921 file2
12 file3

Perl Global variable uninitialized

I'm new to perl so please bear with me.
I have script that is parsing a CSV file. To make things easier to debug I am using a state machine FSA::Rules (works great love it).
Every thing is going well only now I need to make my logs make sense, as part of this I need to record line numbers so my program looks some thing like this.
my $line = '';
my $lineCount = 0;
sub do {
...
#CSV opened
...
#State machine stuff happens here
readLine;
if ($line =~ m/.*Pattern*/){
#do stuff
}
}
sub readLine{
$line = <CSV>;
$lineCount ++;
}
But I get the following error
Use of uninitialized value $line in pattern match (m//) at
Any one know why $line would not be initialized?
Thanks.
When you reach end of file, $line = <CSV> will assign the undefined value to $line. The usual idiom is to check whether the readline function (which is implicitly called by the <> operator) returned a good value or not before proceeding ...
while (my $line = <CSV>) {
# guaranteed that $line has a defined value
...
}
but you with your sequence of calls, you are avoiding that check. Your current code also increments $lineCount even when <CSV> does not return a good value, which may not be what you want either.

Nested while loop which does not seem to keep variables appropriately

I'm an amature Perl coder, and I'm having a lot of trouble figuring what is causing this particular issue. It seems as though it's a variable issue.
sub patch_check {
my $pline;
my $sline;
while (<SYSTEMINFO>) {
chomp($_);
$sline = $_;
while (<PATCHLIST>) {
chomp($_);
$pline = $_;
print "sline $sline pline $pline underscoreline $_ "; #troubleshooting
print "$sline - $pline\n";
if ($pline =~ /($sline)/) {
#print " - match $pline -\n";
}
} #end while
}
}
There is more code, but I don't think it is relevant. When I print $sline in the first loop it works fine, but not in the second loop. I tried making the variables global, but that did not work either.
The point of the subform is I want to open a file (patches) and see if it is in (systeminfo). I also tried reading the files into arrays and doing foreach loops.
Does anyone have another solution?
It looks like your actual goal here is to find lines which are in both files, correct? The normal (and much more efficient! - it only requires you to read in each file once, rather than reading all of one file for each line in the other) way to do this in Perl would be to read the lines from one file into a hash, then use hash lookups on each line in the other file to check for matches.
Untested (but so simple it should work) code:
sub patch_check {
my %slines;
while (<SYSTEMINFO>) {
# Since we'll just be comparing one file's lines
# against the other file's lines, there's no real
# reason to chomp() them
$slines{$_}++;
}
# %slines now has all lines from SYSTEMINFO as its
# keys and the values are the number of times the
# line appears, in case that's interesting to you
while (<PATCHLIST>) {
print "match: $_" if exists $slines{$_};
}
}
Incidentally, if you're reading your data from SYSTEMINFO and PATCHLIST, then you're doing it the old-fashioned way. When you get a chance, read up on lexical filehandles and the three-argument form of open if you're not already familiar with them.
Your code is not entering the PATCHLIST while loop the 2nd time through the SYSTEMINFO while loop because you already read all the contents of PATCHLIST the first time through. You'd have to re-open the PATCHLIST filehandle to accomplish what you're trying to do.
That's a pretty inefficient way to see if the lines of one file match the lines of another file. Take a look at grep with the -f flag for another way.
grep -f PATCHFILE SYSTEMINFO
What I like to do in such cases is: read one file and create keys for a hash from the values you are looking for. And then read the second file and look if the keys are already existing. In this way you have to read each file only once.
Here is example code, untested:
sub patch_check {
my %patches = ();
open(my $PatchList, '<', "patch.txt") or die $!;
open(my $SystemInfo, '<', "SystemInfo.txt") or die $!;
while ( my $PatchRow = <$PatchList> ) {
$patches($PatchRow) = 0;
}
while ( my $SystemRow = <$SystemInfo> ) {
if exists $patches{$SystemRow} {
#The Patch is in System Info
#Do whateever you want
}
}
}
You can not read one file inside the read loop of another. Slurp one file in, then have one loop as a foreach line of the slurped file, the outer loop, the read loop.