【发布时间】:2011-06-20 20:59:40
【问题描述】:
所以,我最近注意到在脚本中使用了 opendir,并希望对其稍作更改,以便它返回目录子文件夹中的文件以及目录本身中的文件。经过调查,我无法为 opendir 找到任何类型的递归选项,并且无法让 glob 返回标量。因此,与其胡扯任何一个,我认为更谨慎的做法是问:获取目录及其子目录中所有文件的句柄的标准方法是什么?
【问题讨论】:
标签: perl
所以,我最近注意到在脚本中使用了 opendir,并希望对其稍作更改,以便它返回目录子文件夹中的文件以及目录本身中的文件。经过调查,我无法为 opendir 找到任何类型的递归选项,并且无法让 glob 返回标量。因此,与其胡扯任何一个,我认为更谨慎的做法是问:获取目录及其子目录中所有文件的句柄的标准方法是什么?
【问题讨论】:
标签: perl
经典的方式是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');
【讨论】:
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 $_ ? find($_) : $_ }。
您需要记住的唯一事情是到目前为止的路径需要预先添加到readdir(),并且它返回. 和..,因此需要消除这些 - 第二个map() 调用(首先应用)做这两件事。如果您知道您正在运行的操作系统,您可以插入 / 或 \ 而不是调用 File::Spec->catfile(),但后者有利于可移植性。
【讨论】:
find?为避免上当,您需要记录每个看到的文件的 inode(或等效文件),这意味着仅扫描 n 个文件需要 O(n) 内存。
File::Spec 时,我都会提到使用File::chdir (p3rl.org/File::chdir)。容易多了!
man find 提到它甚至是POSIX 要求。你是对的,你只需要记录 O(maxdepth) inode。 (对于“单个目录链”,从技术上讲,这可能是 O(n),但我意识到这不是一种常见的情况。)不得不说,我认为正确的做法是让操作系统检查和禁止硬链接循环,以及将目录层次结构处理为始终遵循(在这种情况下,请注意购买者)或从不遵循符号链接的程序。
编辑:我已将这个基本结构作为File::chdir::WalkDir (should be live soon) 上传到CPAN,它会导出一个walkdir,类似于下图。
引用自my answer to another question:
我发现使用完美合作伙伴opendir/readdir 和File::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);
}
}
}
【讨论】:
reslash() 的存在)。
C: 等)。