0

i think this is a simple problem, but i'm stuck with it for some time now! I need a fresh pair of eyes on this.

The thing is i have this code in perl:

#!c:/Perl/bin/perl
use CGI qw/param/;  
use URI::Escape; 

print "Content-type: text/html\n\n";

my $directory = param ('directory'); 
$directory = uri_unescape ($directory); 

my @contents;
readDir($directory);


foreach (@contents) {
  print "$_\n";
}

#------------------------------------------------------------------------
sub readDir(){
 my $dir = shift;
    opendir(DIR, $dir) or die $!;
    while (my $file = readdir(DIR)) {
  next if ($file =~ m/^\./);
  if(-d $dir.$file)
  {
   #print $dir.$file. " ----- DIR\n";
   readDir($dir.$file);
  }
  push @contents, ($dir . $file);
    }
    closedir(DIR);
}

I've tried to make it recursive. I need to have all the files of all of the directories and subdirectories, with the full path, so that i can open the files in the future.

But my output only returns the files in the current directory and the files in the first directory that it finds. If i have 3 folders inside the directory it only shows the first one.

Ex. of cmd call:

"perl readDir.pl directory=C:/PerlTest/"

Thanks

Catarrunas
  • 43
  • 3
  • 7

3 Answers3

1

Avoid wheel reinvention, use CPAN.

use Path::Class::Iterator;
my $it = Path::Class::Iterator->new(
        root => $dir,
        breadth_first => 0
);
until ($it->done) {
        my $f = $it->next;
        push @contents, $f;
}

Make sure that you don't let people set $dir to something that will let them look somewhere you don't want them to look.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
1

Your problem is the scope of the directory handle DIR. DIR has global scope so each recursive call to readDir is using the same DIR; so, when you closdir(DIR) and return to the caller, the caller does a readdir on a closed directory handle and everything stops. The solution is to use a local directory handle:

sub readDir {
    my ($dir) = @_;
    opendir(my $dh, $dir) or die $!;
    while(my $file = readdir($dh)) {
        next if($file eq '.' || $file eq '..');
        my $path = $dir . '/' . $file;
        if(-d $path) {
            readDir($path);
        }
        push(@contents, $path);
    }
    closedir($dh);
}

Also notice that you would be missing a directory separator if (a) it wasn't at the end of $directory or (b) on every recursive call. AFAIK, slashes will be internally converted to backslashes on Windows but you might want to use a path mangling module from CPAN anyway (I only care about Unix systems so I don't have any recommendations).

I'd also recommend that you pass a reference to @contents to readDir rather than leaving it as a global variable, fewer errors and less confusion that way. And don't use parentheses on sub definitions unless you know exactly what they do and what they're for. Some sanity checking and scrubbing on $directory would be a good idea as well.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
0

There are many modules that are available for recursively listing files in a directory.

My favourite is File::Find::Rule

    use strict ;
    use Data::Dumper ;
    use File::Find::Rule ;
    my $dir = shift ;   # get directory from command line

    my @files = File::Find::Rule->in( $dir );
    print Dumper( \@files ) ;

Which sends a list of files into an array ( which your program was doing).

$VAR1 = [
      'testdir',
      'testdir/file1.txt',
      'testdir/file2.txt',
      'testdir/subdir',
      'testdir/subdir/file3.txt'
    ];

There a loads of other options, like only listing files with particular names. Or you can set it up as an iterator, which is described in How can I use File::Find

How can I use File::Find in Perl?

If you want to stick to modules that come with Perl Core, have a look at File::Find.

Community
  • 1
  • 1
justintime
  • 3,601
  • 4
  • 22
  • 37