Eli
Eli

Reputation: 38949

How Can I get a handle to files in a dir and its subdirs?

So, I recently noticed a use of opendir in a script, and would like to change it slightly so that it returns files in the subfolders of a directory as well as files in the directory itself. After investigating, I haven't been able to find any sort of recursive option for opendir, and have had trouble getting glob to return a scalar. So, rather than fudge around with either one more, I figured it would be more prudent to just ask: what is the standard way to get a handle to all files in a dir and its subdirs?

Upvotes: 3

Views: 172

Answers (4)

Joel Berger
Joel Berger

Reputation: 20280

Edit: I have uploaded this basic structure to CPAN as File::chdir::WalkDir (should be live soon), which exports a walkdir which is similar to the one shown below.

Quoted from my answer to another question:

I find that a recursive directory walking function using the perfect partners opendir/readdir and File::chdir (my fav CPAN module, great for cross-platform) allows one to easily and clearly manipulate anything in a directory including subdirectories if desired (if not, omit the recursion).

Example (a simple deep ls):

#!/usr/bin/env perl
use strict;
use warnings;

use File::chdir; #Provides special variable $CWD
# assign $CWD sets working directory
# can be local to a block
# evaluates/stringifies to absolute path
# other great features

walk_dir(shift);

sub do_something {
  print shift . "\n";
}

sub walk_dir {
  my $dir = shift;
  local $CWD = $dir;
  opendir my $dh, $CWD; # lexical opendir, so no closedir needed
  print "In: $CWD\n";

  while (my $entry = readdir $dh) {
    next if ($entry =~ /^\.+$/);
    # other exclusion tests    

    if (-d $entry) {
      walk_dir($entry);
    } elsif (-f $entry) {
      do_something($entry);
    }
  }

}

Upvotes: 0

FMc
FMc

Reputation: 42411

The classic way is with File::Find, which has the advantage of being a core module, but it can be a bit of a pain. If you're able to use a third-party module, File::Util is quite handy:

use File::Util;
my $fu = File::Util->new;

my $root = 'foo/bar';

my @dirs_and_files = $fu->list_dir($root, '--recurse');
my @files_only     = $fu->list_dir($root, '--recurse', '--files-only');

Upvotes: 7

j_random_hacker
j_random_hacker

Reputation: 51226

In truth there's probably a module on CPAN for this, but I'd just do the recursion myself:

use File::Spec;

sub find($) {
    opendir my $dh, $_[0] or die;
    return
        map { $_, -d $_ ? find($_) : () }
        map { /\A\.\.?\z/ ? () : File::Spec->catfile($_[0], $_) } readdir $dh;
}

This includes directories in the results -- if you want only files, replace the first map() call with map { -d $_ ? find($_) : $_ }.

The only things you need to remember are that the path so far needs to be prepended to readdir(), and that it returns . and .. so these need to be eliminated -- the second map() call (which is applied first) does both these things. If you know the OS you're running on, you can just interpolate a / or \ instead of calling File::Spec->catfile(), but the latter is good for portability.

Upvotes: 0

Seth Robertson
Seth Robertson

Reputation: 31451

find2perl produces example code for recursive calls over all files in a directory tree.

> find2perl . -type f -print
#! /usr/bin/perl -w
    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
        if 0; #$running_under_some_shell

use strict;
use File::Find ();

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

sub wanted;



# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, '.');
exit;


sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);

    (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
    -f _ &&
    print("$name\n");
}

Use it as a template as necessary.

Upvotes: 5

Related Questions