Perl 如何获得目录及其子目录中文件的句柄?

Perl 如何获得目录及其子目录中文件的句柄?,perl,Perl,因此,我最近注意到一个脚本中使用了opendir,我想稍微修改一下,这样它可以返回目录子文件夹中的文件以及目录本身中的文件。在调查之后,我无法为opendir找到任何类型的递归选项,并且很难让glob返回标量。因此,我想,与其再胡闹一次,不如问问:获取目录及其子目录中所有文件句柄的标准方法是什么?find2perl生成递归调用目录树中所有文件的示例代码 > find2perl . -type f -print #! /usr/bin/perl -w eval 'exec /usr/

因此,我最近注意到一个脚本中使用了opendir,我想稍微修改一下,这样它可以返回目录子文件夹中的文件以及目录本身中的文件。在调查之后,我无法为opendir找到任何类型的递归选项,并且很难让glob返回标量。因此,我想,与其再胡闹一次,不如问问:获取目录及其子目录中所有文件句柄的标准方法是什么?

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

根据需要将其用作模板。

事实上,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$\uu?find($):$\u}

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

经典的方法是使用,它的优点是作为一个核心模块,但可能有点麻烦。如果您能够使用第三方模块,则非常方便:

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

编辑:我已经将这个基本结构上传到CPAN as(),它导出了一个类似于下图的
walkdir

引自:

我发现,使用perfect partners
opendir
/和(我的fav CPAN模块,非常适合跨平台)的递归目录遍历函数可以方便、清晰地操作目录中的任何内容,包括子目录(如果不需要,则省略递归)

示例(一个简单的深度
ls
):


@你是说通过递归符号链接/硬链接的“无限”目录结构吗?当然可以,但是处理这样的层次结构不会愚弄每一个工具,包括
find
?为了避免被愚弄,您需要记录每个看到的文件的inode(或等效项),这意味着只需扫描n个文件就需要O(n)内存。不,只需O(maxdepth)。find和File::find都检测无限周期。File::Find默认情况下也会排除重复的链接文件,需要O(n)内存,但可以选择不使用。@j_random_hacker,每当我看到
File::Spec
我提到使用
File::chdir
()。容易多了@ysth:谢谢,果然
man find
提到这甚至是POSIX的要求。你是对的,你只需要记录O(maxdepth)索引节点。(从技术上讲,对于“单链目录”,这可能是O(n),但我意识到这不是一种常见的情况。)不得不说,我认为正确的做法是操作系统检查并禁止硬链接循环,处理目录层次结构的程序要么始终遵循(在这种情况下,注意清空)或者永远不要遵循符号链接。@Joel:我喜欢按词汇范围划分当前目录的想法。它感觉非常像C++中的RAII——你不能忘记撤销一个改变。但不确定它是否最适合这里,因为我们还是需要完整的路径名。您似乎不知道和。@daxim:Path::Class看起来不错。File::Next看起来可能无法正确处理Windows路径名(基于
reslash()
)。@Joel:我看到File::chdir不允许在Windows上更改卷(
C:
等)。@daxim没有听说过它们。我来看看。@j_random_hacker,你是对的,但是你不能把工作目录改成新的卷,然后从那里开始吗?
#!/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);
    }
  }

}