【问题标题】:How Can I get a handle to files in a dir and its subdirs?如何获取目录及其子目录中的文件的句柄?
【发布时间】:2011-06-20 20:59:40
【问题描述】:

所以,我最近注意到在脚本中使用了 opendir,并希望对其稍作更改,以便它返回目录子文件夹中的文件以及目录本身中的文件。经过调查,我无法为 opendir 找到任何类型的递归选项,并且无法让 glob 返回标量。因此,与其胡扯任何一个,我认为更谨慎的做法是问:获取目录及其子目录中所有文件的句柄的标准方法是什么?

【问题讨论】:

    标签: perl


    【解决方案1】:

    经典的方式是File::Find,它的优点是是核心模块,但可能有点痛苦。如果您能够使用第三方模块,File::Util 非常方便:

    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');
    

    【讨论】:

      【解决方案2】:

      find2perl 生成递归调用目录树中所有文件的示例代码。

      > 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");
      }
      

      根据需要将其用作模板。

      【讨论】:

        【解决方案3】:

        事实上,CPAN 上可能有一个模块可以解决这个问题,但我只是自己进行递归:

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

        这包括结果中的目录 -- 如果您想要 个文件,请将第一个 map() 调用替换为 map { -d $_ ? find($_) : $_ }

        您需要记住的唯一事情是到目前为止的路径需要预先添加到readdir(),并且它返回...,因此需要消除这些 - 第二个map() 调用(首先应用)做这两件事。如果您知道您正在运行的操作系统,您可以插入 /\ 而不是调用 File::Spec->catfile(),但后者有利于可移植性。

        【讨论】:

        • @ysth:你的意思是通过递归符号链接/硬链接的“无限”目录结构吗?当然,但处理这样的层次结构不会欺骗所有工具,包括find?为避免上当,您需要记录每个看到的文件的 inode(或等效文件),这意味着仅扫描 n 个文件需要 O(n) 内存。
        • 不,只是 O(maxdepth)。 find 和 File::Find 都检测到无限循环。 File::Find 默认也排除重复的链接文件,需要 O(n) 内存,但可以选择不这样做。
        • @j_random_hacker,每当我看到File::Spec 时,我都会提到使用File::chdir (p3rl.org/File::chdir)。容易多了!
        • @ysth:谢谢,果然man find 提到它甚至是POSIX 要求。你是对的,你只需要记录 O(maxdepth) inode。 (对于“单个目录链”,从技术上讲,这可能是 O(n),但我意识到这不是一种常见的情况。)不得不说,我认为正确的做法是让操作系统检查和禁止硬链接循环,以及将目录层次结构处理为始终遵循(在这种情况下,请注意购买者)或从不遵循符号链接的程序。
        • @Joel:我喜欢词法范围当前目录的想法。感觉很像 C++ 中的 RAII——你不能忘记撤消更改。不确定它是否最适合这里,因为无论如何我们都想要完整的路径名。
        【解决方案4】:

        编辑:我已将这个基本结构作为File::chdir::WalkDir (should be live soon) 上传到CPAN,它会导出一个walkdir,类似于下图。

        引用自my answer to another question

        我发现使用完美合作伙伴opendir/readdirFile::chdir(我最喜欢的 CPAN 模块,非常适合跨平台)的递归目录遍历功能允许人们轻松清晰地操作目录中的任何内容,包括子目录(如果需要,则省略递归)。

        例子(一个简单的深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);
            }
          }
        
        }
        

        【讨论】:

        • 你好像不知道File::NextPath::Class
        • @daxim: Path::Class 看起来不错。 File::Next 看起来可能无法正确处理 Windows 路径名(基于 reslash() 的存在)。
        • @Joel:不幸的是,我看到 File::chdir 不允许在 Windows 上更改卷(C: 等)。
        • @daxim,没听说过。我去看看。
        • @j_random_hacker,你是对的,但你不能将工作目录更改为新卷并从那里开始吗?
        猜你喜欢
        • 1970-01-01
        • 2012-07-22
        • 1970-01-01
        • 1970-01-01
        • 2011-03-10
        • 1970-01-01
        • 2012-09-13
        • 1970-01-01
        • 2017-03-15
        相关资源
        最近更新 更多