Reputation: 2501
i have writed some script, that recursively print's directory's content. But it prints warning for each folder. How to fix this?
sample folder:
dev# cd /tmp/test
dev# ls -p -R
test2/
testfile
testfile2./test2:
testfile3
testfile4
my code:
#!/usr/bin/perl
use strict;
use warnings;
browseDir('/tmp/test');
sub browseDir {
my $path = shift;
opendir(my $dir, $path);
while (readdir($dir)) {
next if /^\.{1,2}$/;
if (-d "$path/$_") {
browseDir("$path/$_");
}
print "$path/$_\n";
}
closedir($dir);
}
and the output:
dev# perl /tmp/cotest.pl
/tmp/test/test2/testfile3
/tmp/test/test2/testfile4
Use of uninitialized value $_ in concatenation (.) or string at /tmp/cotest.pl line 16.
/tmp/test/
/tmp/test/testfile
/tmp/test/testfile2
Upvotes: 1
Views: 109
Reputation: 386371
You place a value in $_
before calling browseDir
and you expect it the value to be present after calling browseDir
(a reasonable expectation), but browseDir
modifies that variable.
Just add local $_;
to browseDir
to make sure that any change to it are undone before the sub exits.
Unrelated to your question, here are three other issues:
".\n"
and "..\n"
.Fix:
#!/usr/bin/perl
use strict;
use warnings;
browseDir('/tmp/test');
sub browseDir {
my $path = shift;
opendir(my $dh, $path) or die $!;
my @files = readdir($dh);
closedir($dh);
for (@files) {
next if /^\.{1,2}z/;
if (-d "$path/$_") {
browseDir("$path/$_");
}
print "$path/$_\n";
}
}
Finally, why don't use you a module like File::Find::Rule?
use File::Find::Rule qw( );
print "$_\n" for File::Find::Rule->in('/tmp');
Note: Before 5.12, while (readir($dh))
would have to be written while (defined($_ = readdir($dh)))
Upvotes: 1
Reputation: 107080
Why not use the File::Find module? It's included in almost all distributions of Perl since Perl 5.x. It's not my favorite module due to the sort of messy way it works, but it does a good job.
You define a wanted
subroutine that does what you want and filter out what you don't want. In this case, you're printing pretty much everything, so all wanted
does is print out what is found.
In File::Find
, the name of the file is kept in $File::Find::name
and the directory for that file is in $File::Find::dir
. The $_
is the file itself, and can be used for testing.
Here's a basic way of what you want:
use strict;
use warnings;
use feature qw(say);
use File::Find;
my $directory = `/tmp/test`;
find ( \&wanted, $directory );
sub wanted {
say $File::Find::Name;
}
I prefer to put my wanted
function in my find
subroutine, so they're together. This is equivalent to the above:
use strict;
use warnings;
use feature qw(say);
use File::Find;
my $directory = `/tmp/test`;
find (
sub {
say $File::Find::Name
},
$directory,
);
Good programming says not to print in subroutines. Instead, you should use the subroutine to store and return your data. Unfortunately, find
doesn't return anything at all. You have to use a global array to capture the list of files, and later print them out:
use strict;
use warnings;
use feature qw(say);
use File::Find;
my $directory = `/tmp/test`;
my @directory_list;
find (
sub {
push @directory_list, $File::Find::Name
}, $directory );
for my $file (@directory_list) {
say $file;
}
Or, if you prefer a separate wanted
subroutine:
use strict;
use warnings;
use feature qw(say);
use File::Find;
my $directory = `/tmp/test`;
my @directory_list;
find ( \&wanted, $directory );
sub wanted {
push @directory_list, $File::Find::Name;
}
for my $file (@directory_list) {
say $file;
}
The fact that my wanted subroutine depends upon an array that's not local to the subroutine bothers me which is why I prefer embedding the wanted
subroutine inside my find
call.
One thing you can do is use your subroutine to filter out what you want. Let's say you're only interested in JPG files:
use strict;
use warnings;
use feature qw(say);
use File::Find;
my $directory = `/tmp/test`;
my @directory_list;
find ( \&wanted, $directory );
sub wanted {
next unless /\.jpg$/i; #Skip everything that doesn't have .jpg suffix
push @directory_list, $File::Find::Name;
}
for my $file (@directory_list) {
say $file;
}
Note how the wanted subroutine does a next
on any file I don't want before I push it into my @directory_list
array. Again, I prefer the embedding:
find (sub {
next unless /\.jpg$/i; #Skip everything that doesn't have .jpg suffix
push @directory_list, $File::Find::Name;
}
I know this isn't exactly what you asked, but I just wanted to let you know about the Find::File
module and introduce you to Perl modules (if you didn't already know about them) which can add a lot of functionality to Perl.
Upvotes: 1
Reputation: 1167
May you try that code:
#!/usr/bin/perl
use strict;
use warnings;
browseDir('/tmp');
sub browseDir {
my $path = shift;
opendir(my $dir, $path);
while (readdir($dir)) {
next if /^\.{1,2}$/;
print "$path/$_\n";
if (-d "$path/$_") {
browseDir("$path/$_");
}
}
closedir($dir);
}
If you got that error, its because you call browseDir() before use variable $_.
Upvotes: 1