对于我正在开发的 Perl 脚本,我正在寻找一个快速地和可靠的查找给定目录的所有子目录(传递性)的方法,这些子目录是叶子,即那些没有自己的子目录的子目录。例如,给定以下层次结构:
foo/
foo/bar/
foo/bar/baz
foo/you_fool
我的假设函数,当以"foo"
参数调用时,应该返回 list ("foo/bar/baz/", "foo/you_fool/")
。
因为这显然需要File::Find
或等效的东西,并且已经stat
对其找到的每个文件进行了系统调用,快速地意味着不对stat
每个文件执行另一个操作,尽管stat
每个文件都执行一个额外的操作目录,即 的值$File::Find::dir
是可以的。
因为我的主要目标系统是 Darwin 又名 MacOS,所以不幸的是我无法使用;nlink
字段struct stat
它在该文件系统上似乎没有意义。我知道,在“真正的 Unix”文件系统上,我可以将nlink
每个目录与 2 个进行比较。
如果重要的话,我们可以忽略符号链接、特殊文件和所有其他奇怪的东西;我将要搜索的层次结构非常干净且规则。
答案1
你可以这样做:
perl -MFile::Find -le '
find(sub {
if (-d _) {
undef $leaves{$File::Find::name};
delete $leaves{$File::Find::dir};
}
}, ".");
print for keys %leaves'
undef
将当前目录的哈希元素设置为一个undef
值,同时delete
删除父目录的哈希元素。所以最后哈希的键%leaves
只包含叶子。
对于,我们将重用对当前文件执行的信息,因此不会执行-d _
额外的/ 。对于alone,将执行额外的(not ),这意味着它也会为目录的符号链接返回 true。lstat()
File::Find
lstat()
stat()
-d
stat()
lstat()
虽然它在我的测试中有效,但它可能不是一个有效且面向未来的事情。文档说:
[与“follow”]保证统计数据在调用用户的“wanted()”函数之前已被调用。这可以实现涉及“_”的快速文件检查。请注意,如果出现以下情况,则此保证不再成立:跟随或者快速跟随没有设置。
这样做if (! -l && -d _)
可能会更安全,但代价是lstat()
为每个文件执行额外的操作。
答案2
只是一些想法。我不是 perl 大师,所以不确定 File::Find 能做什么,所以我转向 shell 'find'。
find / -type d -print
打印出从“/”开始的目录列表,因此这是基本列表。我非常怀疑你能否提高 Perl 的速度,尽管 C 应用程序可能可以做到。我怀疑这会是为了微不足道的利益而浪费精力。
GNU find 有一个选项“-printf”,它接受“%h”标志来打印出父目录。因此,您可以做的是同时 -printf 路径 %p 和父路径 %h,然后在 perl 中将父路径拆分到一个新列表中。现在您有了一个不是叶子的路径列表,因此从 %p 列表中删除这些路径,然后就完成了。
遗憾的是 MacOS 没有 GNU 版本,只有一个较低版本。你可以使用 'brew' 安装 GNU find,但是直接在 perl 中从 %p 行创建 %h 的效果并不太难。
最后要注意的一件事。在某些情况下,依赖于管道或类似路径名的换行终止已知会出现错误,因此 GNU find 和(我认为)MacOS find 都对由 \0 而不是 \n 分隔的行提供零终止选项。如果你能使用它,就这样做。
答案3
事实证明,它比我想象的要简单得多,使用了File::Find
我不知道或忘记的功能。这是整个脚本(在我开始添加与问题无关的代码之前);
#! /usr/bin/env perl
use warnings;
use strict;
use File::Find;
use Cwd qw(realpath);
@main::leaves = ();
sub preprocess {
our (@leaves);
my @names = @_;
my @subdirs = grep { $_ ne q(.) && $_ ne q(..) && -d } @names;
push @leaves, $File::Find::dir unless @subdirs;
return @subdirs;
}
sub wanted {
# do nothing at all
}
sub find_leaves {
my @roots = map { realpath($_) } @ARGV;
find({ wanted => \&wanted, preprocess => \&preprocess }, @roots);
}
sub main {
our (@leaves);
@ARGV or push @ARGV, q(.);
find_leaves();
print $_, qq(\n) foreach (@leaves);
# my $num_leaves = $#leaves + 1;
}
main();
__END__