In response to
this SoPW node, I felt compelled to invent something simple and quick that would scan a perl script and report any cases where "my" was used to declare the same variable name more than once.
This approach is not subtle or discerning -- just check all easily-identifyable uses of "my" with variable names, and keep these (with line numbers) in a hash. When done scanning the script, report the cases with more than one line number. (There is also the "--listall" option, which does what you would expect...)
Caveats (at least, the ones I know of):
- The line numbers being reported are based on the output of B::Deparse, where all commentary has been stripped, and spacing and punctuation have been normalized. This means you may need to work a little harder to relate this diagnosis to your original script, but doing it this way helps me make the search for "my" a lot less speculative (and I do save the Deparsed version of the script in a separate file, so you can actually use the line-number info that is being given).
- There are bound to be other ways that "my" shows up in a "deparsed" script besides the ones being searched for in this version, so there may be some "misses".
- Someone is bound to try this on a script containing a quoted literal string that looks like a "my" declaration, so there may be some false alarms as well.
- This doesn't pay any attention to scope. If you use anything like foreach my $arg ( @list ) in multiple places in your script, and these are all sensible and proper, this diagnosis will cite them all as "repeats" anyway -- better to be stupid and safe, I think.
That said, here's the code (tested on a few scripts):
#!/usr/bin/perl
# Program: scan-perl-vars.perl
# Written by: dave graff
# Purpose: look for and summarize the declarations and calls
# of lexically-scoped variables in a perl script
use strict;
my $Usage = "$0 [--listall] my_script.perl\n".
" will list 'my' vars that have been declared more than once in a
+script\n".
" (with --listall, will list all lexical variables, with line numb
+ers)\n";
my $listyp = ' more than once';
if ( @ARGV > 1 and $ARGV[0] eq '--listall' ) {
$listyp = '';
shift;
}
die $Usage unless ( @ARGV == 1 && -r $ARGV[0] );
my $erlog = "/tmp/scn-src.$$.errlog";
my $file = shift;
warn "Deparsing $file...\n";
my @lines = `perl -MO=Deparse,-p $file 2> $erlog`;
die "Nothing deparsed from $file -- sorry.\n"
unless ( @lines );
my $log = `cat $erlog`;
die "Syntax errors in $file -- come back when you're ready.\n"
unless ( $log =~ /^$file syntax OK/ );
# Store an array of subroutine defs in a hash element
# keyed by file name:
my %vardecs; # HoA keyed by var name, stores line #(s) of declaration
+(s)
my $lineno = 0;
open( DP, ">$file.deparse" ) or warn "couldn't write deparsed code to
+$file.deparse:$!\n";
foreach (@lines) {
$lineno++;
print DP "$lineno\t$_";
while (( my $nxt = index( $_, 'my' )) >= 0 ) {
if ( /\bmy\(([^\)]+)/ ) {
foreach my $name (split(/,/,$1)) {
push @{$vardecs{$name}}, $lineno;
}
}
elsif ( /\bmy\s+([\%\$\*\@]\S+)/ ) {
push @{$vardecs{$1}}, $lineno;
my $nxt = 0;
}
$_ = substr( $_, $nxt+3 );
}
}
close DP;
# Now do some reporting
die "No lexically-scoped variables in $file\n" unless ( keys %vardecs
+);
my $nlisted = 0;
foreach (sort keys %vardecs) {
next unless ( scalar @{$vardecs{$_}} > 1 or $listyp eq '');
print join( ' ', $_, @{$vardecs{$_}}, "\n" );
$nlisted++;
}
print "\nTotal of $nlisted 'my' variables declared$listyp in $file\n";
print "(see $file.deparse for correct line-number references)\n"
if ( $nlisted );