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

Hello my wise fellows

I´ve been unable to solve this one, and I hope you can help me out with this message:

Variable "$prev_col" will not stay shared at C:\Site\cgi-bin\import.cgi line 543. Thu Jan 27 17:37:58 2005 import.cgi: Variable "$prev_row" will not stay shared at C:\Site\cgi-bin\import.cgi line 543.

Here´s the code:

... if ( $extension ne "txt" && $extension eq "xls") { my $infile = "../$infile"; my $outfile = "../$outfile"; # Requesting extra modules: use Spreadsheet::ParseExcel; use Data::Dumper; # Openning the file dump open (DUMP, ">$outfile") || warn "Error opening file: $!\n"; select DUMP; # Excel parsing my $parse_excel = new Spreadsheet::ParseExcel(CellHandler => \&cell +_handler, NotSetCell => 1); my $prev_index = -1; my $prev_row = 0; my $prev_col = 0; my $workbook = $parse_excel->Parse("$infile"); # Define our own cell handler to reduce S::PE's memory use. # This function will be called each time a cell is encountered. # However, it will ignore blank cells so we have to keep track # of our location in the worksheet so that we can pad out blank # cells and rows. # sub cell_handler { my $workbook = $_[0]; my $sheet_index = $_[1]; my $row = $_[2]; my $col = $_[3]; my $cell = $_[4]; # Only process the first worksheet if ($sheet_index > 0) { $workbook->ParseAbort(1); return; } # Reset the col counter between rows $prev_col = 0 if $row != $prev_row; # Add tabs between fields and newlines between rows. Also pa +d # any missing rows or columns. # print "\n" for $prev_row +1 .. $row; print "\t" for $prev_col +1 .. $col; # Print the UNformatted value of the cell print $cell->{Val}; # Keep track of where we are $prev_row = $row; $prev_col = $col; } close DUMP; select STDOUT; }
This is a sub that parses excel files, and it´s within a script that handles the importation of a table into a mysql db. I´ve read the possible solution about this, regarding anonymous subs, but I still fail to have the Perl wisdom to implement this solution. Can you guys code it up for me?

Thanks a lot

André

Replies are listed 'Best First'.
Re: Perl complaining about vars not staying shared
by Tanktalus (Canon) on Jan 27, 2005 at 20:02 UTC

    A major shift of code is needed. Move the sub cell_handler to above the parser construction, and remove the name, making it anonymous.

    The problem is that named subs are always global. They cannot bind to variables that are currently "in-scope" (in a surrounding function) that may go out of scope (calling cell_handler outside of the surrounding function). In short, global (named) functions cannot be closures.

    # Excel parsing my $cell_handler = sub { # cell_handler contents go here. }; my $parse_excel = new Spreadsheet::ParseExcel(CellHandler => $cell_han +dler, NotSetCell => 1); #...
      in short, global (named) functions cannot be closures.
      On a point of pedantry, named subs are quite capable of being closures. They are just not (usually) as useful as anonymous closures.

      Dave.

Re: Perl complaining about vars not staying shared
by holli (Abbot) on Jan 27, 2005 at 20:10 UTC
    simply make your "state-variables" package globals.

    holli, regexed monk
Re: Perl complaining about vars not staying shared
by Fang (Pilgrim) on Jan 27, 2005 at 21:55 UTC

    You can find the explanation of such behavior in Paul DuBois's book "MySQL and Perl for the Web". Basically:

    mod_perl runs scripts by wrapping them inside a function (one reason for this is that it can assign each script its own unique namespace). This can cause problems due to variable scope. Here is a simple program that illustrates the effect:
    #!/usr/bin/perl -w print "Content-Type: text/html\n\n"; my $x = 0; f (); exit (0); sub f { print $x++, "\n"; }
    If you request this script several times from your browser, the script doesn’t print 0 each time as you would expect. Instead, the values increase. This occurs even though the variable $x is explicitly initialized to 0. A clue that something is wrong here will be found in the Apache error log:

    Variable "$x" will not stay shared at line 5.

    (The line number is off due to the script being executed inside the wrapper function.) The difficulty here is that when the script is run inside a wrapper by mod_perl, your entire script becomes a single function, and any functions in the script become inner subroutines. That means f() is an inner subroutine that references a lexically scoped my() variable belonging to its parent subroutine and that isn’t passed to it as an argument. As a result, the inner subroutine sees $x with the correct value the first time the script runs. After that, the variable becomes unshared and the inner subroutine continues to use its own unshared value of $x on subsequent invocations. This issue is discussed in more detail in the mod_perl Guide, along with various solutions to the problem.

    I don't know which solutions are offered in the mod_perl Guide, but Paul DuBois offers two:

    • declare the variables to be global with use vars qw($x);
    • pass the variables as the arguments to any subroutine that needs it, so f(); becomes f($x);

    Quick update: I rushed myself a bit, as you don't seem to be using mod_perl. If what I wrote turns out to be irrelevant to your problem, I'll suffer public spanking. Of course, you're invited.

      Thank you all!

      In fact, Fang, I am not using mod_perl yet. But I do plan to. The tip Tan. gave solved the problem: naming the sub with my with a variable name. Thanks a lot, Tan. The point is tricky is not to forget the ; after the closing } , otherwise it won´t work.

      Take care!

      André