使用Perl在unix上获取目录和子目录大小的最快方法是什么?

时间:2010-04-21 08:35:08

标签: perl filesystems

我正在使用Perl stat()函数来获取目录及其子目录的大小。我有一个大约20个父目录的列表,其中有几千个递归子目录,每个子目录都有几百个记录。 脚本的主要计算部分如下所示:

sub getDirSize {
my $dirSize = 0;
my @dirContent = <*>;

my $sizeOfFilesInDir = 0;
foreach my $dirContent (@dirContent) {
   if (-f $dirContent) {
        my $size = (stat($dirContent))[7];
        $dirSize += $size;
   } elsif (-d $dirContent) {
        $dirSize += getDirSize($dirContent);
   } 
}
return $dirSize;
}

脚本执行的时间超过一个小时,我想让它更快。

我尝试使用shell du命令,但du的输出(转换为字节)不准确。而且它也非常耗时。 我正在使用HP-UNIX 11i v1。

7 个答案:

答案 0 :(得分:2)

我曾经遇到类似的问题,并使用并行化方法来加快速度。由于您有大约20个顶级目录,因此这可能是一种非常简单的方法供您尝试。 将顶层目录拆分为多个组(最好是多少组是经验问题),多次调用fork()并分析子进程中的目录大小。在子进程结束时,将结果写入一些临时文件。当所有孩子都完成后,从文件中读取结果并处理它们。

答案 1 :(得分:2)

在sfink和samtregar的帮助下,在perlmonks上试试这个:

#!/usr/bin/perl
use warnings;
use strict;
use File::Find;
my $size = 0;
find( sub { $size += -f $_ ? -s _ : 0 }, shift(@ARGV) );
print $size, "\n";

这里我们正在递归指定目录的所有子目录,获取每个文件的大小,并通过使用特殊的'_'语法进行大小测试,重新使用文件测试中的stat。

我倾向于相信du会足够可靠。

答案 2 :(得分:1)

我看到了几个问题。一个@dirContent显式设置为&lt; *&gt;每次输入getDirSize时都会重置此项。结果将是一个无限循环,至少在你耗尽堆栈之前(因为它是一个递归调用)。其次,有一个特殊的文件句柄符号用于从stat调用中检索信息 - 下划线(_)。见:http://perldoc.perl.org/functions/stat.html。您的代码按原样调用stat三次,以获得基本相同的信息(-f,stat和-d)。由于文件I / O很昂贵,你真正想要的是调用一次stat然后使用“_”引用数据。以下是一些示例代码,我相信它可以完成您要执行的操作

#!/usr/bin/perl

my $size = 0;
getDirSize(".",\$size);

print "Size: $size\n";

sub getDirSize {
  my $dir  = shift;
  my $size = shift;

  opendir(D,"$dir");
  foreach my $dirContent (grep(!/^\.\.?/,readdir(D))) {
     stat("$dir/$dirContent");
     if (-f _) {
       $$size += -s _;
     } elsif (-d _) {
       getDirSize("$dir/$dirContent",$size);
     } 
  }
  closedir(D);
}

答案 3 :(得分:1)

下面是getDirSize()的另一个变体,它不需要引用保存当前大小的变量,并接受一个参数来指示是否应该考虑子目录:

#!/usr/bin/perl

print 'Size (without sub-directories): ' . getDirSize(".") . " bytes\n";
print 'Size (incl. sub-directories): ' . getDirSize(".", 1) . " bytes\n";

sub getDirSize
# Returns the size in bytes of the files in a given directory and eventually its sub-directories
# Parameters:
#   $dirPath (string): the path to the directory to examine
#   $subDirs (optional boolean): FALSE (or missing) = consider only the files in $dirPath, TRUE = include also sub-directories
# Returns:
#   $size (int): the size of the directory's contents
{
  my ($dirPath, $subDirs) = @_;  # Get the parameters

  my $size = 0;

  opendir(my $DH, $dirPath);
  foreach my $dirEntry (readdir($DH))
  {
    stat("${dirPath}/${dirEntry}");  # Stat once and then refer to "_"
    if (-f _)
    {
     # This is a file
     $size += -s _;
    }
    elsif (-d _)
    {
     # This is a sub-directory: add the size of its contents
     $size += getDirSize("${dirPath}/${dirEntry}", 1) if ($subDirs && ($dirEntry ne '.') && ($dirEntry ne '..'));
    } 
  }
  closedir($DH);

  return $size;
}

答案 4 :(得分:0)

每当你想要加快某些事情时,你的第一个任务就是找出什么是缓慢的。使用Devel::NYTProf等分析器来分析程序,找出应该集中精力的地方。

除了重用上一个stat中的数据之外,由于Perl很糟糕,我会摆脱递归。我将构建一个堆栈(或队列)并继续处理它,直到没有任何东西可以处理。

答案 5 :(得分:0)

如果您的主目录是目录和文件inode的绝大多数消费者,那么不要计算它。计算系统的另一半并从中推导出系统其余部分的大小。 (你可以在几毫秒内从df获得用过的磁盘空间')。您可能需要添加一个小的“软糖”因子来获得相同的数字。 (还要记住,如果你将一些可用空间计算为root,那么与Linux上的ext2 / ext3中的其他用户5%相比,你会有一些额外的空间,不知道HPUX)。

答案 6 :(得分:0)

大人们的回答是好的。我做了一些修改,因为我想获得Windows计算机上给定路径下所有文件夹的大小。

这就是我的方法。

#!/usr/bin/perl
use strict;
use warnings;
use File::stat;


my $dirname = "C:\\Users\\xxx\\Documents\\initial-docs";
opendir (my $DIR, $dirname) || die "Error while opening dir $dirname: $!\n";

my $dirCount = 0;
foreach my $dirFileName(sort readdir $DIR)
{

      next if $dirFileName eq '.' or $dirFileName eq '..';

      my $dirFullPath = "$dirname\\$dirFileName";
      #only check if its a dir and skip files
      if (-d $dirFullPath )
      {
          $dirCount++;
          my $dirSize = getDirSize($dirFullPath, 1); #bytes
          my $dirSizeKB = $dirSize/1000;
          my $dirSizeMB = $dirSizeKB/1000;
          my $dirSizeGB = $dirSizeMB/1000;
          print("$dirCount - dir-name: $dirFileName  - Size: $dirSizeMB (MB) ... \n");

      }   
}

print "folders in $dirname: $dirCount ...\n";

sub getDirSize
{
  my ($dirPath, $subDirs) = @_;  # Get the parameters

  my $size = 0;

  opendir(my $DH, $dirPath);
  foreach my $dirEntry (readdir($DH))
  {
    stat("${dirPath}/${dirEntry}");  # Stat once and then refer to "_"
    if (-f _)
    {
     # This is a file
     $size += -s _;
    }
    elsif (-d _)
    {
     # This is a sub-directory: add the size of its contents
     $size += getDirSize("${dirPath}/${dirEntry}", 1) if ($subDirs && ($dirEntry ne '.') && ($dirEntry ne '..'));
    } 
  }
  closedir($DH);

  return $size;
}
1
;

输出:

1 - dir-name: acct-requests  - Size: 0.458696 (MB) ...
2 - dir-name: environments  - Size: 0.771527 (MB) ...
3 - dir-name: logins  - Size: 0.317982 (MB) ...
folders in C:\Users\xxx\Documents\initial-docs: 3 ...