在 perl 中查找没有其他子文件夹的文件夹

find folders with no further subfolders in perl

如何在给定路径中找到没有其他子文件夹的所有文件夹?它们可能包含文件,但没有其他文件夹。

例如,给定以下目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)的输出应该如下:

1
2
3
4
5
6
time/aa/bb/something/*
time/aa/bc/anything/*
time/aa/bc/everything/*
time/ab/cc/here/*
time/ab/cc/there/*
time/ab/cd/everywhere/*

上面的

* 代表文件。


任何时候你想写一个目录遍历器,总是使用标准的 File::Find 模块。在处理文件系统时,您必须能够处理奇怪的极端情况,而幼稚的实现很少这样做。

提供给回调的环境(在文档中命名为 wanted)具有三个变量,它们对您想要做的事情特别有用。

$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

当我们找到一个不是 ... 的目录时,我们记录完整路径并删除它的父目录,我们现在知道它不能是叶目录。最后,任何剩余的记录路径都必须离开,因为 File::Find 中的 find 执行深度优先搜索。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#! /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"$_\
"
for sort keys %dirs;

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

1
2
3
4
5
6
7
$ 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

或使用完整路径

1
2
3
4
5
6
7
$ 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

或在同一调用中检测多个目录。

1
2
3
4
5
6
7
8
9
$ 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
2
sub child_dirs {
    my ($directory) = @_;
  • 打开目录

    1
    opendir my $dir, $directory or die $!;
  • 从文件所在目录的文件中选择文件

    1
    2
    my @subdirs = grep {-d $_ and not m</\\.\\.?$>} map"$directory/$_", readdir $dir;
    #                  ^-- directory and not . or ..  ^-- use full name

  • 如果此类选定文件的列表包含元素,
    3.1。然后递归到每个这样的目录,
    3.2.否则此目录是 "leaf",它将被附加到输出文件中。

    1
    2
    3
    4
    5
    6
    if (@subdirs) {
       return map {child_dirs($_)} @subdirs;
    } else {
       return"$directory/*";
    }
    # OR: @subdirs ? map {child_dirs($_)} @subdirs :"$directory/*";
  • .

    1
    }

    示例用法:

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


    我尝试了 readdir 的做事方式。然后我偶然发现了这个……

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

    我从这个输出中消除了与字符串的初始部分匹配并且没有某些叶条目的任何条目。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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\
    "
    ; }

    }