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