内容

名称

File::Find - 遍历目录树。

概要

use File::Find;
find(\&wanted, @directories_to_search);
sub wanted { ... }

use File::Find;
finddepth(\&wanted, @directories_to_search);
sub wanted { ... }

use File::Find;
find({ wanted => \&process, follow => 1 }, '.');

说明

这些函数用于在目录树中搜索,对找到的每个文件执行类似于 Unix find 命令的操作。File::Find 导出了两个函数,findfinddepth。它们的工作方式类似,但有细微差别。

find
find(\&wanted,  @directories);
find(\%options, @directories);

find() 对给定的 @directories 按给定的顺序进行深度优先搜索。对于找到的每个文件或目录,它都会调用 &wanted 子例程。(有关如何使用 &wanted 函数的详细信息,请参见下文)。此外,对于找到的每个目录,它都会 chdir() 到该目录并继续搜索,对目录中的每个文件或子目录调用 &wanted 函数。

finddepth
finddepth(\&wanted,  @directories);
finddepth(\%options, @directories);

finddepth() 的工作方式与 find() 相同,只是它在对目录的内容调用 &wanted 函数之后才对目录调用该函数。它执行后序遍历而不是前序遍历,从目录树的底部向上工作,而 find() 从树的顶部向下工作。

尽管 finddepth() 函数的名称,但 find()finddepth() 都对目录层次结构执行深度优先搜索。

%options

find() 的第一个参数是 &wanted 函数的代码引用,或描述要对每个文件执行的操作的哈希引用。代码引用将在下文的 “wanted 函数” 中描述。

以下是哈希的可能键

wanted

值应为代码引用。此代码引用将在下文的 “wanted 函数” 中描述。&wanted 子例程是强制性的。

bydepth

仅在报告完目录的所有条目后才报告目录的名称。入口点 finddepth() 是在 find() 的第一个参数中指定 { bydepth => 1 } 的快捷方式。

preprocess

值应为代码引用。此代码引用用于预处理当前目录。当前处理的目录的名称位于 $File::Find::dir 中。预处理函数在 readdir() 之后、调用 wanted() 函数的循环之前调用。它使用字符串列表(实际上是文件/目录名称)调用,并应返回字符串列表。该代码可用于按字母顺序、数字顺序对文件/目录名称进行排序,或根据其名称单独筛选目录条目。当 followfollow_fast 生效时,preprocess 是 no-op。

postprocess

值应为代码引用。它在离开当前处理的目录之前调用。它在无参数的 void 上下文中调用。当前目录的名称位于 $File::Find::dir 中。此挂钩便于对目录进行汇总,例如计算其磁盘使用情况。当 followfollow_fast 生效时,postprocess 是 no-op。

follow

导致跟随符号链接。由于具有符号链接(已跟随)的目录树可能包含多个文件,甚至可能存在循环,因此必须使用每个文件的条目构建哈希。对于大型目录树,这在空间和时间上都可能很昂贵。请参见下文的 "follow_fast""follow_skip"。如果 followfollow_fast 生效

  • 保证在调用用户的 wanted() 函数之前已调用 lstat。这允许涉及 _ 的快速文件检查。请注意,如果未设置 followfollow_fast,则此保证不再成立。

  • 有一个变量 $File::Find::fullname,其中包含已解析所有符号链接的文件的绝对路径名。如果链接是悬空符号链接,则 fullname 将设置为 undef

follow_fast

这类似于 follow,但它可能会多次报告某些文件。但是,它确实检测到循环。由于只需要对符号链接进行哈希处理,因此在空间和时间上都便宜得多。如果多次处理文件(通过用户的 wanted() 函数)比仅仅花费时间更糟糕,则应使用 follow 选项。

follow_skip

follow_skip==1(这是默认值)会导致在即将第二次处理所有既不是目录也不是符号链接的文件时忽略它们。如果即将第二次处理目录或符号链接,则 File::Find 会终止。

follow_skip==0 导致如果即将第二次处理任何文件,则 File::Find 会终止。

follow_skip==2 导致 File::Find 忽略任何重复文件和目录,但正常继续执行。

指定如何处理目标不存在的符号链接。如果为真且为代码引用,则将使用符号链接名称和它所在的目录作为参数调用它。否则,如果为真且警告已开启,则会发出形式为 "symbolic_link_name is a dangling symbolic link\n" 的警告。如果为假,则会静默忽略悬空符号链接。

no_chdir

在递归时不会 chdir() 到每个目录。当然,wanted() 函数需要意识到这一点。在这种情况下,$_ 将与 $File::Find::name 相同。

untaint

如果在 污染模式(-T 命令行开关或 EUID != UID 或 EGID != GID)中使用 find,则在内部必须在目录名称 chdir 到它们之前取消其污染。因此,它们会根据正则表达式 untaint_pattern 进行检查。请注意,传递给用户的 wanted() 函数的所有名称仍然受到污染。如果在非污染模式下使用此选项,则 untaint 是空操作。

untaint_pattern

见上文。这应使用 qr 引用运算符设置。默认设置为 qr|^([-+@\w./]+)$|。请注意,括号至关重要。

untaint_skip

如果设置,则跳过不符合 untaint_pattern 的目录,包括其所有子目录。在这种情况下,默认设置为 die

所需函数

wanted() 函数对每个文件和目录执行所需的任何验证。请注意,尽管名称为 wanted(),但该函数是一个通用回调函数,并且不会告诉 File::Find 文件是否“所需”。实际上,其返回值会被忽略。

所需函数不接受任何参数,而是通过一系列变量执行其工作。

$File::Find::dir 是当前目录名称,
$_ 是该目录内的当前文件名
$File::Find::name 是文件的完整路径名。

上述变量均已本地化,并且可以更改而不影响所需函数之外的数据。

例如,在检查文件 /some/path/foo.ext 时,您将拥有

$File::Find::dir  = /some/path/
$_                = foo.ext
$File::Find::name = /some/path/foo.ext

在调用函数时,您将 chdir() 到 $File::Find::dir,除非指定了 no_chdir。请注意,当更改目录生效时,根目录 (/) 是一个有点特殊的情况,因为 $File::Find::dir'/'$_ 的连接不完全等于 $File::Find::name。下表总结了所有变体

             $File::Find::name  $File::Find::dir  $_
default      /                  /                 .
no_chdir=>0  /etc               /                 etc
             /etc/x             /etc              x

no_chdir=>1  /                  /                 /
             /etc               /                 /etc
             /etc/x             /etc              /etc/x

followfollow_fast 生效时,还会有一个 $File::Find::fullname。该函数可以设置 $File::Find::prune 以修剪树,除非指定了 bydepth。除非指定了 followfollow_fast,为了兼容性原因(find.pl、find2perl),还提供了以下全局变量:$File::Find::topdir$File::Find::topdev$File::Find::topino$File::Find::topmode$File::Find::topnlink

此库对 find2perl 工具很有用(作为 App-find2perl CPAN 分发的部分进行分发),当提供给它时,

find2perl / -name .nfs\* -mtime +7 \
  -exec rm -f {} \; -o -fstype nfs -prune

会生成类似以下内容

sub wanted {
   /^\.nfs.*\z/s &&
   (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_)) &&
   int(-M _) > 7 &&
   unlink($_)
   ||
   ($nlink || (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))) &&
   $dev < 0 &&
   ($File::Find::prune = 1);
}

请注意上面 int(-M _) 中的 __ 是一个神奇的文件句柄,它缓存了前面 stat()lstat() 或文件测试的信息。

这是另一个有趣的 wanted 函数。它将查找所有无法解析的符号链接

sub wanted {
     -l && !-e && print "bogus link: $File::Find::name\n";
}

请注意,您可以在 wanted() 函数要搜索的目录列表中混合目录和(非目录)文件。

find(\&wanted, "./foo", "./bar", "./baz/epsilon");

在上面的示例中,wanted() 将不会评估 ./baz/ 中除 ./baz/epsilon 之外的任何文件。

另请参阅 CPAN 上的脚本 pfind,了解此模块的不错应用。

警告

如果您使用 -w 开关运行程序,或者使用 warnings pragma,File::Find 将为一些奇怪的情况报告警告。您可以通过放置语句来禁用这些警告

no warnings 'File::Find';

在适当的范围内。有关词法警告的更多信息,请参阅 warnings

错误和注意事项

如果您确定要扫描的文件系统反映了父目录 nlink 计数中的子目录数,则可以将变量 $File::Find::dont_use_nlink 设置为 0。

如果您将 $File::Find::dont_use_nlink 设置为 0,您可能会注意到速度有所提高,但如果文件系统未按预期填充 nlink,则可能会导致无法递归进入子目录。

$File::Find::dont_use_nlink 现在在所有平台上默认为 1。

请注意,遵循符号链接的选项可能很危险。根据目录树的结构(包括到目录的符号链接),您可能会多次遍历给定的(物理)目录(仅在 follow_fast 生效时)。此外,删除或更改符号链接目录中的文件可能会导致非常不愉快的意外情况,因为您删除或更改了未知目录中的文件。

历史

如果递归调用,File::Find 过去会产生不正确的结果。在 perl 5.8 开发期间,此错误已修复。File::Find 的第一个修复版本是 1.01。

另请参阅

find(1),find2perl。