tags:

views:

135

answers:

2

Hi, I am trying to write out multiple report files using perl. Each file has the same structure, but with different data. So, my basic code looks something like

#begin code
our $log_fh;
open %log_fh, ">" . $logfile

our $rep;

if (multipleReports)
{
   while (@reports) {
     printReport($report[0]);
   }
}

sub printReports
{
   open $rep, ">" . $[0];
   printHeaders();
   printBody();
   close $rep;
}

sub printHeader() {
format HDR =
@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
$generatedLine
.

format HDR_TOP =
.

$rep->format_name("HDR");
$rep->format_top_name("HDR_TOP");

$generatedLine = "test";
write($rep);
$generatedLine = "next item";
write($rep);
$generatedLine = "last header item";
write($rep);
}

sub printBody #There are multiple such sections in my code. For simplicity, I have just shown 1 here
{
 #declare own header and header top. Set report to use these and print items to $rep
}

#end code

The above is just a high level of the code I am using and I hope I have captured all the salient points. However, for some reason, I get the first report file output correctly. The second file instead of having in the first section

test
next item
last item

reads

last item
last item
last item

I have tried a whole lot of options primarily around autoflush, but, for the life of me can't figure out why it is doing this. I am using Perl 5.8.2. Any help/pointers much appreciated.

Thanks George

Edit 1 I tried passing the filehandle as a parameter to the subroutines, but, was still seeing the issue. I then move the format statement outside the subroutine and declared the $generated variable as a global. This seemed to fix it. I think for some reason declaring the format each time, the subroutine was being called, seemed to be messing it up. Not sure exactly why. Wierd thing though is, I moved the format statement back to inside the subroutine (I didn't like the idea of declaring all the variables in all my format statements to be global). But,this time I changed the declaration to be

my $generatedLine = "";
my $format = "format HDR = \n" .
'@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>' . "\n" .
'$generatedLine' . "\n" .
'.';

my $formatTop = "format HDR_TOP = \n".
'.';

   eval $format;
   eval $formatTop;

This seems to work as well - I see the correct output in my multiple files (well..I am currently only testing with 2 files..I will be doing some more testing tomorrow).

Any idea why declaring the format this way seems to work? Is there something special going on with eval?

Thanks George

+8  A: 

I've never used it, but Text::Report looks promising if you are making complex reports.

Regarding more general matters, your code makes me think you are not running with use strict and use warnings. If not, turn them on. That will provide many clues.

Also, you appear to be using subroutines, which is good, but not taking advantage of one of their primary purposes -- namely, to provide well-defined areas for variable scoping. For example, why does $rep need to be a global variable? If a subroutine needs some information, pass it in as an argument. Especially when your program attempts to perform the same task over and over -- such as generating a bunch of reports -- you need to be careful not to allow variables from one iteration retain stale values during subsequent iterations.

Exercising some basic discipline in the organization of your program will largely solve such problems. Here is a simple illustration.

use strict;
use warnings;

# Example usage: perl script.pl foo.txt bar.txt
main(@ARGV);

sub main {
    my @report_names = @_;
    for my $rep_name (@report_names) {
        my @fake_data = map rand(), 1..10;
        printReport($rep_name, @fake_data);
    }
}

sub printReport {
    my ($rep_name, @data) = @_;
    open my $fh, ">", $rep_name or die $!;
    printHeader($fh);
    printBody($fh, @data);
    close $fh;
}

sub printHeader() {
    my $fh = shift;
    print $fh "Header\n";
}

sub printBody {
    my ($fh, @data) = @_;
    print $fh "Body\n";
    print $fh $_, "\n" for @data;
}
FM
FWIW I'd pass the data around as a reference to avoid copying it every call, but references might be overwhelming at this stage.
Schwern
@Schwern Yes, definitely.
FM
@FM - I will try this out...passing the filehandle as an argument. I do have use strict; and use warnings; in my program. It doesn't throw out any errors/warnings.@FM/Schwern - Thanks for the responses
georgemp
A: 

I think what is happening is that in my original code, the format statement is behaving something like a static. It is being executed only once when the subroutine is called first. The next time the subroutine is called, since, the format is not being evaluated, the format variable $generatedLine points to the previous stale variable from the last call of the subroutine. Calling write() is hence just re-writing that same text from the last subroutine call.
With the new method, it is evaluating format every time the subroutine is called, and hence, the format variable $generatedLine points to the latest local $generatedLine associated with the new subroutine call. So, it prints out the correct data.

georgemp