在 Perl 中检测叶目录

在 Perl 中检测叶目录

对于我正在开发的 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::Findlstat()stat()-dstat()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__

相关内容