【问题标题】:File path into JSON data structureJSON数据结构的文件路径
【发布时间】:2015-11-29 05:06:33
【问题描述】:

我正在做一个磁盘空间报告,它使用File::Find 来收集目录树中的累积大小。

我从File::Find(很容易)得到的是目录名称。

例如:

/path/to/user/username/subdir/anothersubdir/etc

我正在运行File::Find 来收集下面的尺寸:

/path/to/user/username

并构建目录和每个子目录的累积大小报告。

我目前得到的是:

while ( $dir_tree ) {
   %results{$dir_tree} += $blocks * $block_size;
   my @path_arr = split ( "/", $dir_tree ); 
   pop ( @path_arr );
   $dir_tree = join ( "/", @path_arr ); 
}

(是的,我知道这不是很好。)。

这样做的目的是当我stat每个文件时,我将它的大小添加到当前节点和树中的每个父节点。

这足以生成:

username,300M
username/documents,150M
username/documents/excel,50M
username/documents/word,40M
username/work,70M
username/fish,50M,
username/some_other_stuff,30M

但我现在想把它转换成 JSON,更像这样:

{ 
    "name" : "username",
    "size" : "307200",
    "children" : [
        { 
            "name" : "documents",
            "size" : "153750",
            "children" : [
                  { 
                      "name" : "excel",
                      "size" : "51200"
                   }, 
                   {
                       "name" : "word",
                       "size" : "81920"
                   }
             ]
         }
    ]
}

那是因为我打算对此结构进行 D3 可视化 - 大致基于 D3 Zoomable Circle Pack

所以我的问题是 - 整理我的数据的最简洁的方法是什么,以便我可以获得累积(最好是非累积)大小信息,但分层填充哈希。

我在考虑“光标”方法(这次使用File::Spec):

use File::Spec; 
my $data;
my $cursor = \$data; 
foreach my $element ( File::Spec -> splitdir ( $File::Find::dir ) ) {
   $cursor -> {size} += $blocks * $block_size;
   $cursor = $cursor -> {$element} 
}

虽然...这并没有完全创建我正在寻找的数据结构,尤其是因为我们基本上必须通过哈希键进行搜索才能完成该过程的“汇总”部分。

有没有更好的方法来做到这一点?

编辑 - 我已经拥有的更完整示例:

#!/usr/bin/env perl

use strict;
use warnings;

use File::Find;
use Data::Dumper;

my $block_size = 1024;

sub collate_sizes {
    my ( $results_ref, $starting_path ) = @_;
    $starting_path =~ s,/\w+$,/,;
    if ( -f $File::Find::name ) {
        print "$File::Find::name isafile\n";
        my ($dev,   $ino,     $mode, $nlink, $uid,
            $gid,   $rdev,    $size, $atime, $mtime,
            $ctime, $blksize, $blocks
        ) = stat($File::Find::name);

        my $dir_tree = $File::Find::dir;
        $dir_tree =~ s|^$starting_path||g;
        while ($dir_tree) {
            print "Updating $dir_tree\n";
            $$results_ref{$dir_tree} += $blocks * $block_size;
            my @path_arr = split( "/", $dir_tree );
            pop(@path_arr);
            $dir_tree = join( "/", @path_arr );
        }
    }
}

my @users = qw ( user1 user2 );

foreach my $user (@users) {
    my $path = "/home/$user";
    print $path;
    my %results;
    File::Find::find(
        {   wanted   => sub { \&collate_sizes( \%results, $path ) },
            no_chdir => 1
        },
        $path
    );
    print Dumper \%results;

    #would print this to a file in the homedir - to STDOUT for convenience
    foreach my $key ( sort { $results{$b} <=> $results{$a} } keys %results ) {
       print "$key => $results{$key}\n";
    }
}

是的 - 我知道这不是便携式的,并且会做一些有些讨厌的事情。我在这里所做的部分工作是试图改进这一点。 (但目前它是一个基于 Unix 的 homedir 结构,所以没关系)。

【问题讨论】:

  • 你能添加一个完整的例子,我可以懒惰地复制/粘贴吗?
  • 好的,请耐心等待。将不得不将我的脚本缩小一点。
  • 好的。添加了一个最小的示例。 (它省略了很多垃圾,比如一些单位格式和不同的摘要输出)。
  • 似乎是一种非常直接的方法。
  • 是的。但根本不能很好地导出到 JSON。

标签: json perl


【解决方案1】:

如果您自己进行目录扫描而不是使用 File::Find,您自然会得到正确的结构。

sub _scan {
   my ($qfn, $fn) = @_;
   my $node = { name => $fn };

   lstat($qfn)
      or die $!;

   my $size   = -s _;
   my $is_dir = -d _;

   if ($is_dir) {
      my @child_fns = do {
         opendir(my $dh, $qfn)
            or die $!;

         grep !/^\.\.?\z/, readdir($dh);
      };

      my @children;
      for my $child_fn (@child_fns) {
         my $child_node = _scan("$qfn/$child_fn", $child_fn);
         $size += $child_node->{size};
         push @children, $child_node;
      }

      $node->{children} = \@children;
   }

   $node->{size} = $size;
   return $node;
}

其余代码:

#!/usr/bin/perl

use strict;
use warnings;    
no warnings 'recursion';

use File::Basename qw( basename );
use JSON           qw( encode_json );

...    

sub scan { _scan($_[0], basename($_[0])) }

print(encode_json(scan($ARGV[0] // '.')));

【讨论】:

  • _-s _;-d _;有错别字。
  • @simbabque,我想你的意思是我应该使用$_ 而不是_,但你错了。 _ 是由statlstat 填充的句柄。这样,我只做一次系统调用,只需要检查一次错误。
  • 更改为停止关注符号链接
【解决方案2】:

最后,我是这样做的:

File::Find想要子collate_sizes

my $cursor = $data;
foreach my $element (
    File::Spec->splitdir( $File::Find::dir =~ s/^$starting_path//r ) )
{
    $cursor->{$element}->{name} = $element;
    $cursor->{$element}->{size} += $blocks * $block_size;
    $cursor = $cursor->{$element}->{children} //= {};
}

生成嵌套目录名称的哈希。 (name 子元素可能是多余的,但无论如何)。

然后使用(使用JSON)进行后期处理:

my $json_structure = {
    'name'     => $user,
    'size'     => $data->{$user}->{size},
    'children' => [],
};
process_data_to_json( $json_structure, $data->{$user}->{children} );
open( my $json_out, '>', "homedir.json" ) or die $!;
print {$json_out} to_json( $json_structure, { pretty => 1 } );
close($json_out);


sub process_data_to_json {
    my ( $json_cursor, $data_cursor ) = @_;
    if ( ref $data_cursor eq "HASH" ) {
        print "Traversing $key\n";
        my $newelt = {
            'name' => $key,
            'size' => $data_cursor->{$key}->{size},
        };
        push( @{ $json_cursor->{children} }, $newelt );
        process_data_to_json( $newelt, $data_cursor->{$key}->{children} );
    }
}

【讨论】:

  • 我打算提出类似的建议,但没有光标,按照您已经拥有的方式进行处理,然后迭代最终结构以创建第二个用于 JSON 处理的结构。不过还没来得及写。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-24
  • 1970-01-01
相关资源
最近更新 更多