Какой самый быстрый способ получить размер каталога и подкаталогов в unix с использованием Perl? - PullRequest
4 голосов
/ 21 апреля 2010

Я использую функцию 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 ]

2 голосов
/ 04 ноября 2010

С некоторой помощью 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";

Здесь мы повторяем все подкаталоги указанного dir, получая размер каждого файла, и мы повторно используем stat из файлового теста, используя специальный синтаксис '_' для проверки размера.

Я склонен полагать, что du будет достаточно надежным, хотя.

2 голосов
/ 21 апреля 2010

Однажды я столкнулся с подобной проблемой и использовал метод параллелизации, чтобы ускорить ее. Поскольку у вас ~ 20 каталогов высшего уровня, это может быть довольно простой подход для вас. Разделите ваши каталоги верхнего уровня на несколько групп (сколько групп лучше всего - это эмпирический вопрос), несколько раз вызовите fork() и проанализируйте размеры каталогов в дочерних процессах. В конце дочерних процессов запишите результаты в несколько временных файлов. Когда все дети будут готовы, прочитайте результаты из файлов и обработайте их.

1 голос
/ 11 марта 2019

Большой ответ хороший. Я немного изменил его, так как хотел получить размеры всех папок по указанному пути на моем компьютере с 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 ...
1 голос
/ 30 сентября 2016

Ниже приведен еще один вариант 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;
}
1 голос
/ 22 апреля 2010

Всякий раз, когда вы хотите ускорить что-то, ваша первая задача - выяснить, что медленно. Используйте профилировщик, такой как Devel :: NYTProf , чтобы проанализировать программу и выяснить, где вам следует сосредоточить свои усилия.

В дополнение к повторному использованию этих данных из stat , я бы избавился от рекурсии, поскольку Perl ужасен в этом. Я построил бы стек (или очередь) и работал бы над этим, пока не осталось ничего для обработки.

1 голос
/ 21 апреля 2010

Я вижу пару проблем. Один @dirContent явно установлен на <*>, он будет сбрасываться каждый раз, когда вы вводите getDirSize. Результатом будет бесконечный цикл, по крайней мере, до тех пор, пока вы не исчерпаете стек (так как это рекурсивный вызов). Во-вторых, есть специальная запись дескриптора файла для извлечения информации из статистического вызова - подчеркивание (_). См .: http://perldoc.perl.org/functions/stat.html. Ваш код как есть вызывает три раза stat по существу для одной и той же информации (-f, stat и -d). Поскольку файловый ввод-вывод дорог, вам действительно нужно вызвать 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);
}
0 голосов
/ 22 апреля 2010

Если ваш основной каталог является крупнейшим потребителем каталогов и файловых индексов, не рассчитывайте его. Рассчитайте вторую половину системы и выведите из нее размер остальной системы. (Вы можете использовать использованное дисковое пространство от df за пару мс '). Возможно, вам придется добавить небольшой коэффициент «выдумки», чтобы получить те же цифры. (также помните, что если вы подсчитаете некоторое свободное пространство как root, то у вас будет немного больше по сравнению с другими пользователями 5% в ext2 / ext3 в Linux, не знаю о HPUX).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...