Print files and subdirectories of given directory

2019-07-29 18:27发布

问题:

I am trying to get all files and directories from a given directory but I can't specify what is the type (file/ directory). Nothing is being printed. What I am doing wrong and how to solve it. Here is the code:

sub DoSearch {
    my $currNode = shift;
    my $currentDir = opendir (my $dirHandler, $currNode->rootDirectory) or die $!;

    while (my $node = readdir($dirHandler)) {
        if ($node eq '.' or $node eq '..') {
            next;
        }

        print "File: " . $node . "\n" if -f $node;
        print "Directory " . $node . "\n" if -d $node;
   }

   closedir($dirHandler);
}

回答1:

readdir returns only the node name without any path information. The file test operators will look in the current working directory if no path is specified, and because the current directory isn't $currNode->rootDirectory they won't be found

I suggest you use rel2abs from the File::Spec::Functions core module to combine the node name with the path. You can use string concatenation, but the library function takes care of corner cases like whether the directory ends with a slash

It's also worth pointing out that Perl identifiers are most often in snake_case, and people familiar with the language would thank you for not using capital letters. They should especially be avoided for the first character of an identifier, as names like that are reserved for globals like package names

I think your subroutine should look like this

use File::Spec::Functions 'rel2abs';

sub do_search {
    my ($curr_node) = @_;
    my $dir         = $curr_node->rootDirectory;

    opendir my $dh, $dir or die qq{Unable to open directory "$dir": $!};

    while ( my $node = readdir $dh ) {
        next if $node eq '.' or $node eq '..';

        my $fullname = rel2abs($node, $dir);

        print "File:     $node\n" if -f $fullname;
        print "Directory $node\n" if -d $fullname;
   }
}

An alternative method is to set the current working directory to the directory being read. That way there is no need to manipulate file paths, but you would need to save and restore the original working directory before and after changing it

The Cwd core module provides getcwd and your code would look like this

use Cwd 'getcwd';

sub do_search {
    my ($curr_node) = @_;

    my $cwd = getcwd;
    chdir $curr_node->rootDirectory or die $!;

    opendir my $dh, '.' or die $!;

    while ( my $node = readdir $dh ) {
        next if $node eq '.' or $node eq '..';

        print "File: \n" if -f $node;
        print "Directory $node\n" if -d $node;
   }

   chdir $cwd or die $!;
}


回答2:

Use this CPAN Module to get all files and subdirectories recursively.

    use File::Find;        

    find(\&getFile, $dir);
    my @fileList;

    sub getFile{
        print $File::Find::name."\n";
        # Below lines will print only file name. 
        #if ($File::Find::name =~ /.*\/(.*)/ && $1 =~ /\./){
            #push @fileList, $File::Find::name."\n";
        }


回答3:

Already answered, but sometimes is handy not to care with the implementation details and you could use some CPAN modules for hiding such details.

One of them is the wonderful Path::Tiny module.

Your code could be as:

use 5.014;         #strict + feature 'say' + ...
use warnings;
use Path::Tiny;

do_search($_) for @ARGV;

sub do_search {
        my $curr_node = path(shift);
        for my $node ($curr_node->children) {
                say "Directory  : $node" if -d $node;
                say "Plain File : $node" if -f $node;
        }
}

The children method excludes the . and the .. automatically.

You also need understand that the -f test is true only for the real files. So, the above code excludes for example symlinks (whose points to real files), or FIFO files, and so on... Such "files" could be usually opened and read as plain files, therefore somethimes instead of the -f is handy to use the -e && ! -d test (e.g. exists, but not an directory).

The Path::Tiny has some methods for this, e.g. you could write

        for my $node ($curr_node->children) {
                print "Directory  : $node\n" if $node->is_dir;
                print "File       : $node\n" if $node->is_file;
        }

the is_file method is usually DWIM - e.g. does the: -e && ! -d.

Using the Path::Tiny you could also easily extend your function to walk the whole tree using the iterator method:

use 5.014;
use warnings;
use Path::Tiny;

do_search($_) for @ARGV;

sub do_search {

    #maybe you need some error-checking here for the existence of the argument or like...

    my $iterator = path(shift)->iterator({recurse => 1});
    while( my $node = $iterator->() ) {
        say "Directory  : ", $node->absolute if $node->is_dir;
        say "File       : ", $node->absolute if $node->is_file;
    }
}

The above prints the type for all files and directories recursive down from the given argument...

And so on... the Path::Tiny is really worth to have installed.