在 Perl 中查找没有子文件夹的文件夹

3

如何在给定路径中查找所有没有子文件夹的文件夹?它们可能包含文件,但没有进一步的文件夹。

例如,假设有以下目录结构:

time/aa/
time/aa/bb
time/aa/bb/something/*
time/aa/bc
time/aa/bc/anything/*
time/aa/bc/everything/*
time/ab/
time/ab/cc
time/ab/cc/here/*
time/ab/cc/there/*
time/ab/cd
time/ab/cd/everywhere/*
time/ac/
find(time) 的输出应该如下所示:
time/aa/bb/something/*
time/aa/bc/anything/*
time/aa/bc/everything/*
time/ab/cc/here/*
time/ab/cc/there/*
time/ab/cd/everywhere/*

*代表文件。


1
你目前尝试了什么?在哪里遇到了困难?你对已经编写的代码有什么问题?我们不是一个“为我编写解决方案”的网站。如果你不知道从哪里开始,可以看一下File::Find:http://search.cpan.org/~dom/perl-5.12.5/lib/File/Find.pm - Moritz Bunkus
4个回答

8
任何时候你想要编写一个目录遍历器,一定要使用标准的File::Find模块。在处理文件系统时,你必须能够处理奇怪的边角情况,而天真的实现很少能够做到这一点。
回调函数提供给wanted(在文档中命名为wanted)的环境有三个变量,对于你想要做的事情特别有用。

$File::Find::dir是当前目录名称

$_是该目录中的当前文件名

$File::Find::name是文件的完整路径名

当我们发现一个不是...的目录时,我们记录完整路径并删除其父目录,因为我们现在知道它不能是叶子目录。最后,任何剩余的记录路径必须是叶子,因为File::Find中的find执行深度优先搜索
#! /usr/bin/env perl

use strict;
use warnings;

use File::Find;

@ARGV = (".") unless @ARGV;

my %dirs;
sub wanted {
  return unless -d && !/^\.\.?\z/;
  ++$dirs{$File::Find::name};
  delete $dirs{$File::Find::dir};
}

find \&wanted, @ARGV;
print "$_\n" for sort keys %dirs;

您可以针对当前目录的子目录运行它

$ leaf-dirs time
time/aa/bb/something
time/aa/bc/anything
time/aa/bc/everything
time/ab/cc/here
time/ab/cc/there
time/ab/cd/everywhere

或者使用完整路径

$ leaf-dirs /tmp/time
/tmp/time/aa/bb/something
/tmp/time/aa/bc/anything
/tmp/time/aa/bc/everything
/tmp/time/ab/cc/here
/tmp/time/ab/cc/there
/tmp/time/ab/cd/everywhere

或者在同一次调用中连接多个目录。

$ mkdir -p /tmp/foo/bar/baz/quux
$ leaf-dirs /tmp/time /tmp/foo
/tmp/foo/bar/baz/quux
/tmp/time/aa/bb/something
/tmp/time/aa/bc/anything
/tmp/time/aa/bc/everything
/tmp/time/ab/cc/here
/tmp/time/ab/cc/there
/tmp/time/ab/cd/everywhere

1

基本上,您打开根文件夹并使用以下过程:

sub child_dirs {
    my ($directory) = @_;
  1. 打开目录

    opendir my $dir, $directory or die $!;
    
  2. 此目录中的文件选择文件,其中文件是一个目录

    my @subdirs = grep {-d $_ and not m</\.\.?$>} map "$directory/$_", readdir $dir;
    #                  ^-- 目录而不是 . 或 ..  ^-- 使用完整名称
    
  3. 如果所选文件列表包含元素,
    3.1. 那么递归进入每个这样的目录,
    3.2. 否则,此目录是一个“叶子”,它将被附加到输出文件中。

    if (@subdirs) {
       return map {child_dirs($_)} @subdirs;
    } else {
       return "$directory/*";
    }
    # 或者:@subdirs ? map {child_dirs($_)} @subdirs : "$directory/*";
    

.

}

使用示例:

say $_ for child_dirs("time"); # dir `time' has to be in current directory.

很好的使用 grep 来缩短代码! - Ilion

0

这个函数可以实现。只需使用您的初始路径调用它:

sub isChild {

  my $folder = shift;
  my $isChild = 1;

    opendir(my $dh, $folder) || die "can't opendir $folder: $!";
    while (readdir($dh)) {
      next if (/^\.{1,2}$/); # skip . and ..
      if (-d "$folder/$_") {
        $isChild = 0;
        isChild("$folder/$_");
      }
    }

    closedir $dh;

    if ($isChild) { print "$folder\n"; }

}

0

我尝试了使用readdir的方法。然后我偶然发现了这个...

  use File::Find::Rule;
  # find all the subdirectories of a given directory
  my @subdirs = File::Find::Rule->directory->in( $directory );

我从输出结果中删除了任何与字符串初始部分匹配且没有一些叶子条目的条目。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接