Perl文件查找模块之File::Find
2013-07-04 15:07:24 阿炯

它是Perl核心模块之一,用于文件的搜索及查找。Perl之中的File::Find模块对在整个文件系统中搜索文件名十分有用,特别是你可以使用正则来匹配文件名并循环地穿过任何的目录结构(许可情况下)。为了展示它的工作方式,我将给出一个使用File::Find模块的脚本实例。

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

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

File::Find的两个方法:
find 是自上而下遍历。

finddepth 是自下而上遍历。
二者调用格式相同,上例中 finddepth(\&del_svn, "."),第一个参数是调用的子函数,第二个参数是根目录。

这里简单的说一下 find 函数,find 函数的用法如下:
find(\&wanted, @directories_to_search);

find()函数包含两个参数,子程序的引用和目录列表。第一个参数是一个代码的引用或者是一个hash引用对于每个文件。

其中第一个参数 wanted 是个子函数,也就是回调函数,由你自己来定义,这个参数必须有,即使它的内容为空。第二个参数是个目录列表,一般情况下,我们可能只是处理一个目录,所以它也可以是个普通的表示目录名的字串标量。

需要注意的是,&wanted 子程序的前面需要加一反斜杠转义。

与文件及其路径相关的三个属性:
$File::Find::dir 是当前目录全路径。
$_ 是当前文件名(不含路径)。
$File::Find::name 是当前文件的完整路径。

$File::Find::dir is the current directory name,
$_ is the current filename within that directory
$File::Find::name is the complete pathname to the file.

 以文件'/some/path/foo.ext '为例:
$File::Find::dir  = /some/path/
$_  = foo.ext
$File::Find::name = /some/path/foo.ext

File::Find的属性
在使用过程中,可以设置其具体属性来改变其行为,最终得到想要结果。
find(\%options, @directories);

finddepth(\%options, @directories);

其实上面所提及的wanted函数,就是这个%options中的最重要的key了。

Perl脚本可以寻找以.tmp, .chk或是.zip结尾的文件或是以~符号开始的文件来完成,脚本将输出它找到的每一个文件的完整路径,而且在最后会显示出所占用的字节数。

在标准Perl库和Perl函数中设置了一个模块,因此当你的机器上安装了Perl之后,所有必要的模块就都可以使用了,File::Find函数模仿了UNIX的find命令并将穿过一个文件树。这里是此方式的API:
Find(&yoursubroutine, ‘dir1’, ‘dir2’…);

提供的子程序,将在后面详细叙述,还有希望进行搜索的目录的列表,记住这些目录将以一种深度优先方式被穿过,使用的另一个方式就是stat()函数(与C的同名库函数类似)。

File::Find方式具有特殊变量,将被赋予特定的信息,显示如下:
* $_包含目录中的当前文件名
* $File::Find::dir包含当前目录名
* $File::Find::name包含$File::Find::dir/$_

当子程序被调用时,就会确实位于变量$File::Find::dir的目录中,子程序使用常规表达式来与$_匹配,$_使用一个if声明来寻找我们前面详细给出的所有文件名。

如果在$_之中存储的文件名与if声明中的五个常规表达式中的任何一个相匹配,我们就将在其下面输入代码块,常规表达式非常的简单,“.”代表一个文字上的点号而不是常规表达式中的“.”的特殊意义,我们使用“”符号来避开特殊意义。“$”代表一个字符串最后的匹配而“^”代表与开头匹配。下表显示了我们试图将其与相对应的常规表达式相匹配的文件。
File that ends with .zip     /.zip$/
File that ends with .tmp     /.tmp$/
File that ends with .TMP     /.TMP$/
File that begins with ~     /^~/
File that ends with .chk     /.chk/

注意:脚本对小写的tmp和大写的TMP同时进行查找,而出于效率方面的考虑,可以将文件名改为大写并只查找TMP匹配。

最后,脚本使用stat()函数来记录所有与if声明之中的某个条件相匹配的文件所使用的字节数。如果条件符合,脚本将存储$size之中的值并将其加入到$ByteCount记录变量,如下面的代码所示:
$ByteCount += $size;

perl下的File::Find模块具有shell下的find命令的功能,下面具体看2个例子:

1、找出某个目录下面以*.old结尾的文件
use strict;
use File::Find;
my $path = '/home/test/';
sub wanted {
if ( -f $File::Find::name ) {
 if ( $File::Find::name =~ /\.old$/ ) {
  print "$File::Find::name\n";
  }
 }
}
find( \&wanted, $path );

2、找出某个目录下面几天前的文件
my $path = '/home/test/';
opendir DH, $path or die "cannot chdir to $path : $!";
for my $file (readdir DH) {
 next if $file eq "." or $file eq "..";
 next if $file =~ /^\./;
 if (time() - (stat($path.$file))[8] > (60*60*24*7)) {
  print $path.$file."\n";
 }
}
closedir DH;

Perl中的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 }, '.');

主要有两个函数,find和finddepth,这两个方法大致相同,但也有细微的差别:
find:
find(/&wanted,  @directories);
find(/%options, @directories);
find()会在@directories的参数所给定的目录中顺序的进行深度优先的查找,对于每一个找到的 文件 或者目录,都会调用&wanted引用所指向的sub(函数)。$wanted函数的具体使用见下文。

另外,对于每一个找到的目录,它都会chdir()到这个目录内部,然后递归的在这个目录中 继续调用&wanted sub来操作这个目录下的子目录或文件。

finddepth:
finddepth(/&wanted,  @directories);
finddepth(/%options, @directories);

finddepth()函数的用法和find()几乎一样,只不过它会在查找完当前目录之后 ,再进入当前 目录的子目录中进行查找。它采用了后序遍历算法取代了前序遍历算法。

wanted函数:
在wanted()函数中,可以对文件和目录做任何形式的判断和确认。需要注意的是:wanted() 函数实质上是一个回调函数,它的返回值被忽略。 wanted函数没有参数,但是会包含一系列的内部变量,如:
$File::Find::dir     是当前目录的名字
$_     是当前正在处理的文件名
$File::Find::name     是当前文件的完整路径+文件名

以上变量都已经被包含在wanted函数中,并且不会影响函数外的同名变量。 例如,如果要检查文件
/some/path/foo.ext
,这些变量的值就是:
$File::Find::dir  = /some/path/
$_                = foo.ext
$File::Find::name = /some/path/foo.ext

程序运行时将会chdir()到$File::Find::dir目录中,除非没有子目录了。

程序示例:
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);
}

比较重要的参数有:wanted、bydepth、no_chdir

wanted
它将指向一代码块,其中包含了对每一个找到的'项'和处理逻辑。

bydepth
在将目录符合条件文件列表输出后,再输出该目录名。

no_chdir
当置为'false'时,在其工作过程中,不进入(chdir)其所在的当前目录。下面是其默认、false、true三种情况的对比:
             $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

$find::Find::prune 可停止将find()函数移到该目录中
use File::Find;
find(\&wanted,'/root/bin');
sub wanted{
 #停止搜索sys目录
 $File::Find::prune = 1 if /sys/;
 if (/\.log/i) {
  print "FILE: $_\n";
  print "Dir: $File::Find::dir\n";
  print "Path: $File::Find::name\n";
 }
}

参考文档:
File::Find

该文章最后由 Administrator 于 2014-02-24 21:17:18 更新,目前是第 2 版。