【问题标题】:What's the fastest way to get directory and subdirs size on unix using Perl?使用 Perl 在 unix 上获取目录和子目录大小的最快方法是什么?
【发布时间】:2010-04-21 08:35:08
【问题描述】:

我正在使用 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。

【问题讨论】:

  • 为什么说du的输出不准确呢?那么什么对你来说是准确的呢? du 是一个 C 程序,它旨在使用相对较好的算法为您获取文件和文件夹的大小。我相信它在执行时会比自定义 Perl 脚本更快。

标签: perl filesystems


【解决方案1】:

在 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";

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

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

【讨论】:

    【解决方案2】:

    每当您想加快某件事的速度时,您的首要任务就是找出什么是慢的。使用诸如Devel::NYTProf 之类的分析器来分析程序并找出应该集中精力的地方。

    除了重用来自最后一个stat 的数据之外,我还要摆脱递归,因为 Perl 很糟糕。我会构建一个堆栈(或一个队列)并对其进行处理,直到没有任何东西需要处理。

    【讨论】:

      【解决方案3】:

      Bigs 的回答很好。我稍微修改了一下,因为我想在我的 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 ...
      

      【讨论】:

        【解决方案4】:

        下面是 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;
        }
        

        【讨论】:

          【解决方案5】:

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

          【讨论】:

          • 好建议。我将进行几次测试,看看在分叉之间分配顶级目录的最佳方式是什么。谢谢。
          【解决方案6】:

          我发现了几个问题。一个@dirContent 显式设置为,每次输入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);
          }
          

          【讨论】:

          • 谢谢。现在它的工作速度提高了大约 10%。我的@dirContent = ;是一个错字,我忘了在活动目录前加上 *。
          【解决方案7】:

          如果您的主目录绝对是目录和文件 inode 的最大使用者,则不要计算它。计算系统的另一半并从中推断出系统其余部分的大小。 (您可以在几毫秒内从df 获得已用磁盘空间)。您可能需要添加一个小的“软糖”因素才能获得相同的数字。 (也请记住,如果您将一些可用空间计算为root,那么与其他用户相比,在 Linux 上的 ext2/ext3 中,您将有一些额外的 5%,不了解 HPUX)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-10-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-01-24
            • 1970-01-01
            相关资源
            最近更新 更多