我如何使用perl以递归方式搜索目录和所有子目录

时间:2013-05-23 00:08:07

标签: perl glob

我使用glob

看到了this link

虽然这不是我想做的事。

这是我的计划。为了在目录中搜索与字符串部分匹配的任何文件,将其作为参数提供给我的函数,比如/home/username/sampledata和字符串,请说data

我为用户提供了一个选项,允许用户在执行时包含一个标志,以执行是否检查子目录,目前默认情况下脚本不包含子目录。

包含子目录的伪代码看起来像这样。

我保存文件路径的数组是全局的

  @fpaths;

  foo($dir);

  sub foo{
      get a tmp array of all files

      for ($i=0 ; $i<@tmp ; $i++) {
          next if ( $tmp[$i]is a hidden file and !$hidden) ; #hidden is a flag too

          if($tmp[$i] is file) {
               push (@fpaths, $dir.$tmp[$i]);
          }
          if($tmp[$i] is dir) {
               foo($dir.$tmp[$i]);
          }

       }
   }

看起来很稳固。

我希望实现的是保存完整路径名的每个文件的数组。

我不知道该怎么做的部分是获取每个文件的列表。希望这可以用glob来完成。

我已经能够使用opendir / readdir来读取每个文件,如果我知道如何检查结果是文件还是目录,我可以再次执行此操作。

所以我的问题是:

  1. 如何使用glob和路径名来获取每个文件/子目录的数组

  2. 如何检查以前找到的数组中的项目是目录还是文件

  3. 谢谢大家

5 个答案:

答案 0 :(得分:9)

我会使用File::Find

请注意File::Find::name是给定文件的完整路径。其中包括目录,因为它们也是文件。

这只是读者想要了解其余细节的一个示例。

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

my $path = "/home/cblack/tests";

find(\&wanted, $path);

sub wanted {
   return if ! -e; 

   print "$File::Find::name\n" if $File::Find::name =~ /foo/;
   print "$File::Find::dir\n" if $File::Find::dir =~ /foo/;
}

更好的是,如果你想将所有这些推到列表中,你可以这样做:

use File::Find;

main();

sub main {
    my $path = "/home/cblack/Misc/Tests";
    my $dirs = [];
    my $files= [];
    my $wanted = sub { _wanted($dirs, $files) };

    find($wanted, $path);
    print "files: @$files\n";
    print "dirs: @$dirs\n";
}

sub _wanted {
   return if ! -e; 
   my ($dirs, $files) = @_;

   push( @$files, $File::Find::name ) if $File::Find::name=~ /foo/;
   push( @$dirs, $File::Find::dir ) if $File::Find::dir =~ /foo/;
}

答案 1 :(得分:3)

  • 我不明白为什么glob解决了如何检查目录条目是文件还是目录的问题。如果您之前一直使用readdir,那么请坚持使用

  • 不要忘记你必须小心处理链接,否则你的递归可能永远不会结束

  • 还要记住,readdir会返回...以及真实的目录内容

  • 使用-f and -d检查节点名称是文件还是目录,但请记住,如果它的loaction不是您当前的工作目录,那么您必须通过添加路径来完全限定它,否则你将谈论一个可能不存在的完全不同的节点

  • 除非这是一次学习经历,否则你最好还是写一些现成的和经过测试的东西,比如File::Find

答案 2 :(得分:2)

Nima Soroush's answer的启发,这里有一个广义递归通配函数,与Bash 4的return cat; 选项类似,允许在子树的所有级别上进行匹配与globstar

<强>实施例

**

注意:虽然此功能将# Match all *.txt and *.bak files located anywhere in the current # directory's subtree. globex '**/{*.txt,*.bak}' # Find all *.pm files anywhere in the subtrees of the directories in the # module search path, @INC; follow symlinks. globex '{' . (join ',', @INC) . '}/**/*.pm', { follow => 1 } 与内置File::Find功能结合在一起,但如果您熟悉glob&#39,则此功能可能会按预期运行; s行为,排序和符号链接行为有很多细微之处 - 请参阅底部的注释。

glob的显着偏差是给定模式参数中的空格被视为模式的一部分;指定多个模式,将它们作为单独的模式参数传递或使用大括号表达式,如上例所示。

源代码

glob()

<强>评论

sub globex {

  use File::Find;
  use File::Spec;
  use File::Basename;
  use File::Glob qw/bsd_glob GLOB_BRACE GLOB_NOMAGIC GLOB_QUOTE GLOB_TILDE GLOB_ALPHASORT/;

  my @patterns = @_;
  # Set the flags to use with bsd_glob() to emulate default glob() behavior.
  my $globflags = GLOB_BRACE | GLOB_NOMAGIC | GLOB_QUOTE | GLOB_TILDE | GLOB_ALPHASORT;
  my $followsymlinks;
  my $includehiddendirs;
  if (ref($patterns[-1]) eq 'HASH') {
    my $opthash = pop @patterns;
    $followsymlinks = $opthash->{follow};
    $includehiddendirs = $opthash->{hiddendirs};
  }
  unless (@patterns) { return };

  my @matches;
  my $ensuredot;
  my $removedot;
  # Use fc(), the casefolding function for case-insensitive comparison, if available.
  my $cmpfunc = defined &CORE::fc ? \&CORE::fc : \&CORE::lc;

  for (@patterns) {
    my ($startdir, $anywhereglob) = split '(?:^|/)\*\*(?:/|$)';
    if (defined $anywhereglob) {  # recursive glob
      if ($startdir) {
        $ensuredot = 1 if m'\./'; # if pattern starts with '.', ensure it is prepended to all results
      } elsif (m'^/') { # pattern starts with root dir, '/'
        $startdir = '/';
      } else { # pattern starts with '**'; must start recursion with '.', but remove it from results
        $removedot = 1;
        $startdir = '.';
      }
      unless ($anywhereglob) { $anywhereglob = '*'; }
      my $terminator = m'/$' ? '/' : '';
      # Apply glob() to the start dir. as well, as it may be a pattern itself.
      my @startdirs = bsd_glob $startdir, $globflags or next;
      find({
          wanted => sub {
            # Ignore symlinks, unless told otherwise.
            unless ($followsymlinks) { -l $File::Find::name and return; }
            # Ignore non-directories and '..'; we only operate on 
            # subdirectories, where we do our own globbing.
            ($_ ne '..' and -d) or return;
            # Skip hidden dirs., unless told otherwise.
            unless ($includehiddendirs) {  return if basename($_) =~ m'^\..'; }
            my $globraw;
            # Glob without './', if it wasn't part of the input pattern.
            if ($removedot and m'^\./(.+)$') { 
              $_ = $1;
            }
            $globraw = File::Spec->catfile($_, $anywhereglob);
            # Ensure a './' prefix, if the input pattern had it.
            # Note that File::Spec->catfile() removes it.
            if($ensuredot) {
              $globraw = './' . $globraw if $globraw !~ m'\./';
            }
            push @matches, bsd_glob $globraw . $terminator, $globflags;
          },
          no_chdir => 1,
          follow_fast => $followsymlinks, follow_skip => 2,
          # Pre-sort the items case-insensitively so that subdirs. are processed in sort order.
          # NOTE: Unfortunately, the preprocess sub is only called if follow_fast (or follow) are FALSE.
          preprocess => sub { return sort { &$cmpfunc($a) cmp &$cmpfunc($b) } @_; }
        }, 
        @startdirs);
    } else {  # simple glob
      push @matches, bsd_glob($_, $globflags);
    }
  }
  return @matches;
}

答案 3 :(得分:1)

您可以将此方法用作分隔特定文件类型的递归文件搜索

my @files;
push @files, list_dir($outputDir);

sub list_dir {
        my @dirs = @_;
        my @files;
        find({ wanted => sub { push @files, glob "\"$_/*.txt\"" } , no_chdir => 1 }, @dirs);
        return @files;
}

答案 4 :(得分:0)

我尝试仅使用 readdir 来实现这一点。我把我的代码留在这里,以防它对任何人有用:

sub rlist_files{
    my @depth = ($_[0],);
    my @files;
    while ($#depth > -1){
        my $dir = pop(@depth);
        opendir(my $dh, $dir) || die "Can't open $dir: $!";
        while (readdir $dh){
            my $entry = "$dir/$_";
            if (!($entry =~ /\/\.+$/)){
                if (-f $entry){
                    push(@files,$entry);
                }
                elsif (-d $entry){
                    push(@depth, $entry);
                }
            }
        }
        closedir $dh;
    }
    return @files;
}

编辑:正如 @brian d foy 所指出的那样,该代码根本不考虑符号链接。

作为练习,我尝试编写一个能够递归跟踪符号链接(可选)而不会陷入循环并且以某种方式有限使用内存(使用哈希来跟踪访问的符号链接是使用几个 GB 的大内存)的新子运行)。当我在做的时候,我还添加了传递正则表达式来过滤文件的选项。同样,我把我的代码留在这里,以防它对任何人有用:

sub rlist_files_nohash{
    use Cwd qw(abs_path);
    my $input_path = abs_path($_[0]);
    if (!defined $input_path){
        die "Cannot find $_[0]."
    }
    my $ignore_symlinks = 0;
    if ($#_>=1){
        $ignore_symlinks = $_[1];
    }
    my $regex;
    if ($#_==2){
        $regex = $_[2];
    }   
    my @depth = ($input_path,);
    my @files;
    my @link_dirs;
    while ($#depth > -1){
        my $dir = pop(@depth);
        opendir(my $dh, $dir) or die "Can't open $dir: $!";
        while (readdir $dh){
            my $entry = "$dir/$_";
            if (!($entry =~ /\/\.+$/)){
                if (-l $entry){
                    if ($ignore_symlinks){
                        $entry = undef;
                    }
                    else{
                        while (defined $entry && -l $entry){
                            $entry = readlink($entry);
                            if (defined $entry){
                                if (substr($entry, 0, 1) ne "/"){
                                    $entry = $dir."/".$entry;
                                }
                                $entry = abs_path($entry);
                            }
                        }
                        if (defined $entry && -d $entry){
                            if ($input_path eq substr($entry,0,length($input_path))){
                                $entry = undef;
                            }
                            else{
                                for (my $i = $#link_dirs;($i >= 0 && defined $entry); $i--){
                                    if (length($link_dirs[$i]) <= length($entry) && $link_dirs[$i] eq substr($entry,0,length($link_dirs[$i]))){
                                        $entry = undef;
                                        $i = $#link_dirs +1;
                                    }
                                }
                                if(defined $entry){
                                    push(@link_dirs, $entry);
                                }
                            }
                        }
                    }
                }
                if (defined $entry){
                    if (-f $entry && (!defined $regex || $entry =~ /$regex/)){
                        push(@files, abs_path($entry));
                    }
                    elsif (-d $entry){
                        push(@depth, abs_path($entry));
                    }
                }
            }
        }
        closedir $dh;
    }
    if ($ignore_symlinks == 0){
        @files = sort @files;
        my @indices = (0,);
        for (my $i = 1;$i <= $#files; $i++){
            if ($files[$i] ne $files[$i-1]){
                push(@indices, $i);
            }
        }
        @files = @files[@indices];
    }
    return @files;
}
#Testing
my $t0 = time();
my @files = rlist_files_nohash("/home/user/", 0, qr/\.pdf$/);
my $tf = time() - $t0;
for my file(@files){
    print($file."\n");
}
print ("Total files found: ".scalar @files."\n");
print ("Execution time: $tf\n");
相关问题