ImDrPatil
ImDrPatil

Reputation: 197

How to read directories and sub-directories without knowing the directory name in perl?

Hi i want to read directories and sub-directories without knowing the directory name. Current directory is "D:/Temp". 'Temp' has sub-directories like 'A1','A2'. Again 'A1' has sub-directories like 'B1','B2'. Again 'B1' has sub-directories like 'C1','C2'. Perl script doesn't know these directories. So it has to first find directory and then read one file at a time in dir 'C1' once all files are read in 'C1' it should changes to dir 'C2'. I tried with below code here i don't want to read all files in array(@files) but need one file at time. In array @dir elements should be as fallows.

$dir[0] = "D:/Temp/A1/B1/C1"
$dir[1] = "D:/Temp/A1/B1/C2"
$dir[2] = "D:/Temp/A1/B2/C1"

Below is the code i tried.

    use strict;
    use File::Find::Rule;
    use Data::Dumper;

    my $dir = "D:/Temp";
    my @dir = File::Find::Rule->directory->in($dir);
    print Dumper (\@dir);
    my $readDir = $dir[3];
    opendir ( DIR, $readDir ) || die "Error in opening dir $readDir\n";
    my @files = grep { !/^\.\.?$/ } readdir DIR;
    print STDERR "files: @files \n\n";

    for my $fil (@files) {
        open (F, "<$fil");
        read (F, my $data);
        close (F);
        print "$data";
    }

Upvotes: 4

Views: 1655

Answers (5)

DavidRR
DavidRR

Reputation: 19387

Your Goal: Find the absolute paths to those directories that do not themselves have child directories.

I'll call those directories of interest terminal directories. Here's the prototype for a function that I believe provides the convenience you are looking for. The function returns its result as a list.

my @list = find_terminal_directories($full_or_partial_path);

And here's an implementation of find_terminal_directories(). Note that this implementation does not require the use of any global variables. Also note the use of a private helper function that is called recursively.

On my Windows 7 system, for the input directory C:/Perl/lib/Test, I get the output:

== List of Terminal Folders ==
c:/Perl/lib/Test/Builder/IO
c:/Perl/lib/Test/Builder/Tester
c:/Perl/lib/Test/Perl/Critic

== List of Files in each Terminal Folder: ==
c:/Perl/lib/Test/Builder/IO/Scalar.pm
c:/Perl/lib/Test/Builder/Tester/Color.pm
c:/Perl/lib/Test/Perl/Critic/Policy.pm

Implementation

#!/usr/bin/env perl

use strict;
use warnings;
use Cwd qw(abs_path getcwd);

my @dir_list = find_terminal_directories("C:/Perl/lib/Test");
print "== List of Terminal Directories ==\n";
print join("\n", @dir_list), "\n";

print "\n== List of Files in each Terminal Directory: ==\n";
for my $dir (@dir_list) {
    for my $file (<"$dir/*">) {
        print "$file\n";
        open my $fh, '<', $file or die $!;
        my $data = <$fh>;  # slurp entire file contents into $data
        close $fh;

        # Now, do something with $data !
    }
}

sub find_terminal_directories {
    my $rootdir = shift;

    my @wanted;
    my $cwd = getcwd();
    chdir $rootdir;
    find_terminal_directories_helper(".", \@wanted);
    chdir $cwd;
    return @wanted;
}

sub find_terminal_directories_helper {
    my ($dir, $wanted) = @_;
    return if ! -d $dir;
    opendir(my $dh, $dir) or die "open directory error!";
    my $count = 0;
    foreach my $child (readdir($dh)) {
        my $abs_child = abs_path($child);
        next if (! -d $child || $child eq "." || $child eq "..");
        ++$count;
        chdir $child;
        find_terminal_directories_helper($abs_child, $wanted);  # recursion!
        chdir "..";
    }
    push @$wanted, abs_path($dir) if ! $count;  # no sub-directories found!
}

Upvotes: 2

Kenosis
Kenosis

Reputation: 6204

Perhaps the following will be helpful:

use strict;
use warnings;
use File::Find::Rule;

my $dir = "D:/Temp";
local $/;

my @dirs =
  sort File::Find::Rule->exec( sub { File::Find::Rule->directory->in($_) == 1 }
  )->directory->in($dir);

for my $dir (@dirs) {
    for my $file (<"$dir/*">) {
        open my $fh, '<', $file or die $!;
        my $data = <$fh>;
        close $fh;
        print $data;
    }
}
  • local $/; lets us slurp the file's contents into a variable. Delete it if you only want to read the first line.
  • The sub in the exec() is used to pass only those dirs which don't contain a dir
  • sort is used to arrange those dirs in your wanted order
  • A file glob <"$dir/*"> is used to get the files in each dir

Edit: Have modified the code to find only 'terminal directories.' Thanks to DavidRR for this spec clarification.

Upvotes: 2

Miller
Miller

Reputation: 35198

use File::Find;

use strict;
use warnings;

my @dirs;
my %has_children;

find(sub {
    if (-d) {
        push @dirs, $File::Find::name;
        $has_children{$File::Find::dir} = 1;
    }
}, 'D:/Temp');

my @ends = grep {! $has_children{$_}} @dirs;

print "$_\n" for (@ends);

Upvotes: 3

chrsblck
chrsblck

Reputation: 4088

I would use File::Find

Sample script:

#!/usr/bin/perl

use strict; 
use warnings;

use File::Find;

my $dir = "/home/chris";

find(\&wanted, $dir);

sub wanted {
    print "dir: $File::Find::dir\n";
    print "file in dir: $_\n";
    print "complete path to file: $File::Find::name\n";
}

OUTPUTS:

$ test.pl
dir: /home/chris/test_dir
file in dir: test_dir2
complete path to file: /home/chris/test_dir/test_dir2
dir: /home/chris/test_dir/test_dir2
file in dir: foo.txt
complete path to file: /home/chris/test_dir/test_dir2/foo.txt
...

Upvotes: 0

bf2020
bf2020

Reputation: 732

Using backticks, write subdirs and files to a file called filelist:

`ls -R $dir > filelist`

Upvotes: -1

Related Questions