I'm writing Perl code that takes a directory and its subdirectories and files, and then prints it to an Excel sheet using Excel::Writer::XLSX
. The issue I'm facing is some of my directories have numerical names like "1", "2.4".
Here's my code:
use Excel::Writer::XLSX;
my $workbook= Excel::Writer::XLSX->new( 'myExcel.xlsx' );
my $ws = $workbook->add_worksheet();
$path = "path_name";
{
$i= 0;
$j = 0;
sub dir_listing {
my ($root) = @_;
$root .= '/';
for my $f (glob "$root*")
{
if (-d $f)
{
$f =~ m/.*\/(.*)/;
$ws->write($i,$j,$1);
$j++;
dir_listing($f);
$j--;
}
if (-f $f)
{
$f =~ m/.*\/(.*)/;
$ws->write($i,$j,$1);
$i++;
}
}
}
}
dir_listing($path);
It's parsing through the numerical directories well and good, but it's not printing the directory name in the Excel sheet.
Argument "" isn't numeric in addition (+) at /home/priyansh.mehta/perl5/lib/perl5/Excel/Writer/XLSX/Worksheet.pm line 2510.
is the warning it shows after execution
I don't need the full path of the directory and files, only the names, hence the use of regex.
The problem seems to be that the write()
function in Excel::Writer::XLSX
does not take a copy of the argument passed, but rather uses aliases (accessing the @_
array) of the arguments. This does not work if the argument is a regex match variable. According to perldoc perlvar:
Regular expression variables allow the programmer to access the state of the most recent successful regex match in the current dynamic scope.
The variables themselves are global and unscoped, but the data they access is scoped similarly to dynamically scoped variables, in that every successful match behaves as though it localizes a global state object to the current block or file scope. (See "Compound Statements" in perlsyn for more details on dynamic scoping and the local keyword.)
The problem is that module function does not access the aliased argument immediately, but first does some regex matching which resets $1
locally to the empty string.
You can work around the issue by not passing the regex match variable directly, but instead make a copy of it into another lexical variable and pass that variable instead.
See perldoc perlsub for more on the @_
variable.
The reason you only observed the issue for numerical directory names and not with other directory names, was that the write()
method did not perform regular expression operations when the argument was non numerical, hence the match variable $1
was not reset in those cases.
So to summarize, it is enough to stringify the match variable when calling the write()
function to work around this issue, so the following should work:
use Excel::Writer::XLSX;
my $workbook= Excel::Writer::XLSX->new( 'myExcel.xlsx' );
my $ws = $workbook->add_worksheet();
my $path = "path_name";
{
my $i= 0;
my $j = 0;
sub dir_listing {
my ($root) = @_;
$root .= '/';
for my $f (glob "$root*")
{
if (-d $f)
{
$f =~ m/.*\/(.*)/;
# Stringify $1 to avoid passing an alias to $1
$ws->write($i, $j, "$1");
$j++;
dir_listing($f);
$j--;
}
if (-f $f)
{
$f =~ m/.*\/(.*)/;
# Stringify $1 to avoid passing an alias to $1
$ws->write($i, $j, "$1");
$i++;
}
}
}
}
dir_listing($path);