如何使用File :: Find创建递归数据结构?

时间:2015-03-24 15:49:37

标签: perl

我正在尝试创建一个表示目录树的递归数据结构。它将通过解析实际目录树来创建。结果应该看起来像这个简化的例子:

$structure = [
    {
        type => 'directory',
        name => 'my_root_directory',
        items => [
            {
                type => 'file',
                name => 'image1.jpg'
            },
            {
                type => 'file',
                name => 'image2.jpg'
            },
            {
                type => 'directory',
                name => 'a_subdirectory',
                items => [
                    {
                        type => 'image',
                        name => 'image2.jpg'
                    }
                ]
            }
        ]
    }
]

过去我通过编写递归函数创建了许多维护难题,所以这次我决定只使用File::Find来处理我的目录。这是我试过的:

use strict;
use warnings;
use File::Find;

my $structure = [];
find(\&wanted, 'my_root_directory');

sub wanted {
    my $node = {};
    if (-f) {
        $node->{type} = 'file';
        $node->{name} = $_;
    }            
    if (-d) {
        $node->{type} = 'directory',
        $node->{name} = $_;
        $node->{items} = # HELP NEEDED!
    }
    push @{$structure}, $node;
}

这不起作用有两个原因:

  1. 我无法指定$node->{items}而无需复制File::Find已经完成的工作。
  2. 即使我填充节点的项目(即通过调用readdir),最终的push语句也只是将节点附加到结束@{$structure}数组。所有等级都将丢失。
  3. 如何使用File::Find创建递归数据结构?

2 个答案:

答案 0 :(得分:3)

您需要与源结构有多相似?我问的原因是 - 这样的事情可能有用:

#!/usr/bin/perl

use strict;
use warnings;
use File::Find;
use Data::Dumper;
use File::Spec::Functions qw(splitdir);

my %tree;

sub insert_into_tree {
    my $cursor = \%tree;
    print "$File::Find::dir = $File::Find::name\n";
    foreach my $subdir ( splitdir($File::Find::dir) ) {
        $cursor->{type} = 'directory';
        $cursor = $cursor->{subdirs}->{$subdir} ||= {};
        $cursor->{name} = $subdir;
    }
    if ( -f $File::Find::name ) {
        push( @{ $cursor->{files} }, $_ );
    }
    else { 
       $cursor -> {type} = 'directory';
    }
}

find( \&insert_into_tree, 'c:\\temp' );
print Dumper \%tree;

它没有做你想要的,但希望有助于说明一般概念?您根据当前文件路径创建一个“光标”,它是对树位置的引用。因此,您可以随意构建目录树,然后插入“文件”数组...所有文件。

你用File::Find刷新的东西是,为你做了一个递归遍历(这很好,因为有了问题),但是wanted函数本身并不是递归的 - 它是一个回调函数,并且每找到一个条目就会触发一次,并将3个变量用于处理。

如此有效 - 你仍然必须自己'建造'你的树。这是$cursor上面做的事情 - 它正在根据传递给File::Find的目录结构制作指向数据结构的指针。

答案 1 :(得分:3)

这将输出您要求的树结构中的文件内容。我使用散列%parent_nodes来填充目录节点的引用,这些目录节点遇到目录节点,由目录路径索引。这使得引用适当的父目录节点以插入新文件节点成为可能。

#!/usr/bin/perl

use strict;
use warnings;
use File::Find;
use File::Basename qw(basename);
use Data::Dumper;

my $root_dir = shift @ARGV || die "Usage: $0 DIR_PATH\n";

my %parent_nodes;
my $structure = [];
find(\&wanted, $root_dir);


sub wanted {
  my $file_name = $_;
  my $file_path = $File::Find::name;
  my $dir_path  = $File::Find::dir;

  if (-f) {
    my $node = {
      type => 'file',
      name => $file_name,
    };

    my $parent_node = $parent_nodes{$dir_path}
      || die "can't find parent node for $dir_path\n";

    push @{$parent_node->{items}}, $node;
  }
  elsif (-d) {

    my $dir_name  = basename($file_path);

    my $node = {
      type => 'directory',
      name => $dir_name,
      items => [],
    };

    if (my $parent_node = $parent_nodes{$dir_path}) {
      push @{$parent_node->{items}}, $node;
    }
    else {
      # this must be the root node
      push @$structure, $node;
    }

    $parent_nodes{$file_path} = $node;
  }
}

print Dumper($structure);