我使用glob
看到了this link虽然这不是我想做的事。
这是我的计划。为了在目录中搜索与字符串部分匹配的任何文件,将其作为参数提供给我的函数,比如/home/username/sampledata
和字符串,请说data
。
我为用户提供了一个选项,允许用户在执行时包含一个标志,以执行是否检查子目录,目前默认情况下脚本不包含子目录。
包含子目录的伪代码看起来像这样。
我保存文件路径的数组是全局的
@fpaths;
foo($dir);
sub foo{
get a tmp array of all files
for ($i=0 ; $i<@tmp ; $i++) {
next if ( $tmp[$i]is a hidden file and !$hidden) ; #hidden is a flag too
if($tmp[$i] is file) {
push (@fpaths, $dir.$tmp[$i]);
}
if($tmp[$i] is dir) {
foo($dir.$tmp[$i]);
}
}
}
看起来很稳固。
我希望实现的是保存完整路径名的每个文件的数组。
我不知道该怎么做的部分是获取每个文件的列表。希望这可以用glob来完成。
我已经能够使用opendir
/ readdir
来读取每个文件,如果我知道如何检查结果是文件还是目录,我可以再次执行此操作。
所以我的问题是:
如何使用glob
和路径名来获取每个文件/子目录的数组
如何检查以前找到的数组中的项目是目录还是文件
谢谢大家
答案 0 :(得分:9)
我会使用File::Find
请注意File::Find::name
是给定文件的完整路径。其中包括目录,因为它们也是文件。
这只是读者想要了解其余细节的一个示例。
use warnings;
use strict;
use File::Find;
my $path = "/home/cblack/tests";
find(\&wanted, $path);
sub wanted {
return if ! -e;
print "$File::Find::name\n" if $File::Find::name =~ /foo/;
print "$File::Find::dir\n" if $File::Find::dir =~ /foo/;
}
更好的是,如果你想将所有这些推到列表中,你可以这样做:
use File::Find;
main();
sub main {
my $path = "/home/cblack/Misc/Tests";
my $dirs = [];
my $files= [];
my $wanted = sub { _wanted($dirs, $files) };
find($wanted, $path);
print "files: @$files\n";
print "dirs: @$dirs\n";
}
sub _wanted {
return if ! -e;
my ($dirs, $files) = @_;
push( @$files, $File::Find::name ) if $File::Find::name=~ /foo/;
push( @$dirs, $File::Find::dir ) if $File::Find::dir =~ /foo/;
}
答案 1 :(得分:3)
我不明白为什么glob
解决了如何检查目录条目是文件还是目录的问题。如果您之前一直使用readdir
,那么请坚持使用
不要忘记你必须小心处理链接,否则你的递归可能永远不会结束
还要记住,readdir
会返回.
和..
以及真实的目录内容
使用-f
and -d
检查节点名称是文件还是目录,但请记住,如果它的loaction不是您当前的工作目录,那么您必须通过添加路径来完全限定它,否则你将谈论一个可能不存在的完全不同的节点
除非这是一次学习经历,否则你最好还是写一些现成的和经过测试的东西,比如File::Find
答案 2 :(得分:2)
受Nima Soroush's answer的启发,这里有一个广义递归通配函数,与Bash 4的return cat;
选项类似,允许在子树的所有级别上进行匹配与globstar
。
<强>实施例强>:
**
注意:虽然此功能将# Match all *.txt and *.bak files located anywhere in the current
# directory's subtree.
globex '**/{*.txt,*.bak}'
# Find all *.pm files anywhere in the subtrees of the directories in the
# module search path, @INC; follow symlinks.
globex '{' . (join ',', @INC) . '}/**/*.pm', { follow => 1 }
与内置File::Find
功能结合在一起,但如果您熟悉glob
&#39,则此功能可能会按预期运行; s行为,排序和符号链接行为有很多细微之处 - 请参阅底部的注释。
与glob
的显着偏差是给定模式参数中的空格被视为模式的一部分;指定多个模式,将它们作为单独的模式参数传递或使用大括号表达式,如上例所示。
源代码
glob()
<强>评论强>
sub globex {
use File::Find;
use File::Spec;
use File::Basename;
use File::Glob qw/bsd_glob GLOB_BRACE GLOB_NOMAGIC GLOB_QUOTE GLOB_TILDE GLOB_ALPHASORT/;
my @patterns = @_;
# Set the flags to use with bsd_glob() to emulate default glob() behavior.
my $globflags = GLOB_BRACE | GLOB_NOMAGIC | GLOB_QUOTE | GLOB_TILDE | GLOB_ALPHASORT;
my $followsymlinks;
my $includehiddendirs;
if (ref($patterns[-1]) eq 'HASH') {
my $opthash = pop @patterns;
$followsymlinks = $opthash->{follow};
$includehiddendirs = $opthash->{hiddendirs};
}
unless (@patterns) { return };
my @matches;
my $ensuredot;
my $removedot;
# Use fc(), the casefolding function for case-insensitive comparison, if available.
my $cmpfunc = defined &CORE::fc ? \&CORE::fc : \&CORE::lc;
for (@patterns) {
my ($startdir, $anywhereglob) = split '(?:^|/)\*\*(?:/|$)';
if (defined $anywhereglob) { # recursive glob
if ($startdir) {
$ensuredot = 1 if m'\./'; # if pattern starts with '.', ensure it is prepended to all results
} elsif (m'^/') { # pattern starts with root dir, '/'
$startdir = '/';
} else { # pattern starts with '**'; must start recursion with '.', but remove it from results
$removedot = 1;
$startdir = '.';
}
unless ($anywhereglob) { $anywhereglob = '*'; }
my $terminator = m'/$' ? '/' : '';
# Apply glob() to the start dir. as well, as it may be a pattern itself.
my @startdirs = bsd_glob $startdir, $globflags or next;
find({
wanted => sub {
# Ignore symlinks, unless told otherwise.
unless ($followsymlinks) { -l $File::Find::name and return; }
# Ignore non-directories and '..'; we only operate on
# subdirectories, where we do our own globbing.
($_ ne '..' and -d) or return;
# Skip hidden dirs., unless told otherwise.
unless ($includehiddendirs) { return if basename($_) =~ m'^\..'; }
my $globraw;
# Glob without './', if it wasn't part of the input pattern.
if ($removedot and m'^\./(.+)$') {
$_ = $1;
}
$globraw = File::Spec->catfile($_, $anywhereglob);
# Ensure a './' prefix, if the input pattern had it.
# Note that File::Spec->catfile() removes it.
if($ensuredot) {
$globraw = './' . $globraw if $globraw !~ m'\./';
}
push @matches, bsd_glob $globraw . $terminator, $globflags;
},
no_chdir => 1,
follow_fast => $followsymlinks, follow_skip => 2,
# Pre-sort the items case-insensitively so that subdirs. are processed in sort order.
# NOTE: Unfortunately, the preprocess sub is only called if follow_fast (or follow) are FALSE.
preprocess => sub { return sort { &$cmpfunc($a) cmp &$cmpfunc($b) } @_; }
},
@startdirs);
} else { # simple glob
push @matches, bsd_glob($_, $globflags);
}
}
return @matches;
}
答案 3 :(得分:1)
您可以将此方法用作分隔特定文件类型的递归文件搜索
my @files;
push @files, list_dir($outputDir);
sub list_dir {
my @dirs = @_;
my @files;
find({ wanted => sub { push @files, glob "\"$_/*.txt\"" } , no_chdir => 1 }, @dirs);
return @files;
}
答案 4 :(得分:0)
我尝试仅使用 readdir 来实现这一点。我把我的代码留在这里,以防它对任何人有用:
sub rlist_files{
my @depth = ($_[0],);
my @files;
while ($#depth > -1){
my $dir = pop(@depth);
opendir(my $dh, $dir) || die "Can't open $dir: $!";
while (readdir $dh){
my $entry = "$dir/$_";
if (!($entry =~ /\/\.+$/)){
if (-f $entry){
push(@files,$entry);
}
elsif (-d $entry){
push(@depth, $entry);
}
}
}
closedir $dh;
}
return @files;
}
编辑:正如 @brian d foy 所指出的那样,该代码根本不考虑符号链接。
作为练习,我尝试编写一个能够递归跟踪符号链接(可选)而不会陷入循环并且以某种方式有限使用内存(使用哈希来跟踪访问的符号链接是使用几个 GB 的大内存)的新子运行)。当我在做的时候,我还添加了传递正则表达式来过滤文件的选项。同样,我把我的代码留在这里,以防它对任何人有用:
sub rlist_files_nohash{
use Cwd qw(abs_path);
my $input_path = abs_path($_[0]);
if (!defined $input_path){
die "Cannot find $_[0]."
}
my $ignore_symlinks = 0;
if ($#_>=1){
$ignore_symlinks = $_[1];
}
my $regex;
if ($#_==2){
$regex = $_[2];
}
my @depth = ($input_path,);
my @files;
my @link_dirs;
while ($#depth > -1){
my $dir = pop(@depth);
opendir(my $dh, $dir) or die "Can't open $dir: $!";
while (readdir $dh){
my $entry = "$dir/$_";
if (!($entry =~ /\/\.+$/)){
if (-l $entry){
if ($ignore_symlinks){
$entry = undef;
}
else{
while (defined $entry && -l $entry){
$entry = readlink($entry);
if (defined $entry){
if (substr($entry, 0, 1) ne "/"){
$entry = $dir."/".$entry;
}
$entry = abs_path($entry);
}
}
if (defined $entry && -d $entry){
if ($input_path eq substr($entry,0,length($input_path))){
$entry = undef;
}
else{
for (my $i = $#link_dirs;($i >= 0 && defined $entry); $i--){
if (length($link_dirs[$i]) <= length($entry) && $link_dirs[$i] eq substr($entry,0,length($link_dirs[$i]))){
$entry = undef;
$i = $#link_dirs +1;
}
}
if(defined $entry){
push(@link_dirs, $entry);
}
}
}
}
}
if (defined $entry){
if (-f $entry && (!defined $regex || $entry =~ /$regex/)){
push(@files, abs_path($entry));
}
elsif (-d $entry){
push(@depth, abs_path($entry));
}
}
}
}
closedir $dh;
}
if ($ignore_symlinks == 0){
@files = sort @files;
my @indices = (0,);
for (my $i = 1;$i <= $#files; $i++){
if ($files[$i] ne $files[$i-1]){
push(@indices, $i);
}
}
@files = @files[@indices];
}
return @files;
}
#Testing
my $t0 = time();
my @files = rlist_files_nohash("/home/user/", 0, qr/\.pdf$/);
my $tf = time() - $t0;
for my file(@files){
print($file."\n");
}
print ("Total files found: ".scalar @files."\n");
print ("Execution time: $tf\n");