jospan has asked for the wisdom of the Perl Monks concerning the following question:

Hi,
I'm 1 month into learning Perl and done reading "Minimal Perl" by Tim Maher (which I enjoyed enoumously). I'm not a programmer by profession but want to use Perl to automate various tasks at my job. I have a problem (obviously) and are looking for your much appreciated help.

THE TASK:
1) I need to test the records from a text file with fields delimited by semicolon (infile.txt).
2) At the end of the test I need to generate a report containing the tested records (outfile.txt).
3) I want to use the implicit file loop available by the -n invocation option to go about the task in an AWKish way (but using AWK is not an option).
4) By way of the implicit loop the read lines from infile.txt should be pushed into an array.
5) In the END block the contents of the array should be written to the outfile.

My test file (infile.txt) contains 3 records of 3 semicolon delimited fields each:
1;2;3 a;b;c 4;5;6
The script is invoked from the command line by:
c:\perl test.pl infile.txt

My environment is Strawberry Perl, v.5.10.1 on Windows XP.

My stripped down script (stripped of everything irrelevant to the problem) is called test.pl:
#! usr/bin/perl -wlnaF';' use strict; { my $outfile = "outfile.txt"; my @result; # Assign fields to variables my $var1 = $F[0]; my $var2 = $F[1]; my $var3 = $F[2]; # Push fields into an array push @result, ( "$var1 $var2 $var3" ); # This debug print shows all 3 infile lines were processed print "Processed infile line no.: $."; END { # This debug print shows only 1 element in array print "Array contains: " . @result . " element(s)."; # pass array by reference and filename by value writeToFile( \@result, $outfile ); } } sub writeToFile { # Takes a reference to an array and a filename as input # Writes the contents of the array to the file my $array_ref = $_[0]; my $filename = $_[1]; open OUTFILE, "> $filename" or die; foreach ( @{ $array_ref } ) { print OUTFILE; } close OUTFILE; }
THE PROBLEM:
Allthough all 3 records from the infile gets processed (a print statement reveals this during processing) only the first record makes it to the array and from the array to the outfile (another print statement and the outfile reveals this).

What am I doing wrong? - pls help!

Best regards,
John

Replies are listed 'Best First'.
Re: Push records to array during implicit loop and write to file
by Anonymous Monk on Jan 12, 2010 at 01:15 UTC
    The implicit loop is messing with your scoping, B::Deparse show you
    BEGIN { $^W = 1; } BEGIN { $/ = "\n"; $\ = "\n"; } LINE: while (defined($_ = <ARGV>)) { chomp $_; our(@F) = split(/;/, $_, 0); use strict 'refs'; { my $outfile = 'outfile.txt'; my @result; my $var1 = $F[0]; my $var2 = $F[1]; my $var3 = $F[2]; push @result, "$var1 $var2 $var3"; print "Processed infile line no.: $."; sub END { print 'Array contains: ' . @result . ' element(s).'; writeToFile(\@result, $outfile); } ; } sub writeToFile { my $array_ref = $_[0]; my $filename = $_[1]; die unless open OUTFILE, "> $filename"; foreach $_ (@{$array_ref;}) { print OUTFILE $_; } close OUTFILE; } ; }
    Every time you read a line you @result is empty. The implicit loop is best left to oneliners.

    See Coping with Scoping

      Thanks for putting me on the right track, Anonymous Monk.

      The problem is now SOLVED! :-)

      By declaring @result private with 'my' it apparantly gets reset every time the script loops through the next line from the infile.

      The solution (probably not a nice one) was to declare @result a global variable with 'our' in stead of 'my'.
        You have a rather restrictive set of requirements. The implicit loop, using these other options like "naF" as well using an END block as you described aren't usually seen.

        I guess it is possible that this is some homework assignment. If it is, then I think this is not a very good one because it uses some features that just aren't used that often.

        A more typical thing would be like shown below. There would be some way to get a "usage statement" if the input command was wrong. The input/output "opens" would be done near the beginning of the program. You don't want to have a program that runs a long time, then bombs because the output permissions or path to the output file means that no result can be output!

        If you want to process multiple files, I show a simple foreach loop below that will do that. There is no need to close a filehandle before you open it to another file.

        Below I show "my @result" just needs to be declared at a higher scope that the input loop. An "our" variable is usually used for situations where a variable needs visibility outside the package it was defined in, which is not the case here.

        Anyway, if your requirement statements aren't driven by a homework assignment, I would do something a lot more straightforward.

        #! usr/bin/perl -w use strict; if (@ARGV == 0) { print "usage: perl test.pl infile [infile]\n"; exit(1); } my $outfile = "outfile.txt"; open (OUTFILE, '>', $outfile) or die "unable to open $outfile : $!\n"; my @result; foreach my $infile (@ARGV) { open (INFILE, '<', $infile) or die "unable to open $infile : $!\n"; while (<INFILE>) { chomp; my (@vars) = split(/;/,$_); push @result, "@vars"; print "Processed $infile line no.: $. vars= @vars\n"; } } print "Result Array contains: " . @result . " element(s).\n"; writeToFile( \@result); sub writeToFile { my $array_ref = shift; foreach ( @{ $array_ref } ) { print OUTFILE "$_\n"; } } __END__ infile.txt: 1;2;3 a;b;c 4;5;6 infile2.txt: X;Y;Z 9;10;11 C:\TEMP>perl newbie3.pl infile.txt infile2.txt Processed infile.txt line no.: 1 vars= 1 2 3 Processed infile.txt line no.: 2 vars= a b c Processed infile.txt line no.: 3 vars= 4 5 6 Processed infile2.txt line no.: 4 vars= X Y Z Processed infile2.txt line no.: 5 vars= 9 10 11 Result Array contains: 5 element(s). C:\TEMP>cat outfile.txt 1 2 3 a b c 4 5 6 X Y Z 9 10 11