Suic
Suic

Reputation: 2501

directory tree warning

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

Answers (3)

ikegami
ikegami

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:

  • Not even minimal error checking!
  • You could run out of directory handles will navigating a deep directory.
  • You filter out files ".\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

David W.
David W.

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

Xavier S.
Xavier S.

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

Related Questions