самый быстрый способ суммировать размеры файлов по владельцу в каталоге - PullRequest
2 голосов
/ 21 марта 2019

Я использую приведенную ниже команду с использованием псевдонима для печати суммы всех размеров файлов по владельцу в каталоге

ls -l $dir | awk ' NF>3 { file[$3]+=$5 } \
END { for( i in file) { ss=file[i]; \
if(ss >=1024*1024*1024 ) {size=ss/1024/1024/1024; unit="G"} else \ 
if(ss>=1024*1024) {size=ss/1024/1024; unit="M"} else {size=ss/1024; unit="K"}; \
format="%.2f%s"; res=sprintf(format,size,unit); \
printf "%-8s %12d\t%s\n",res,file[i],i }}' | sort -k2 -nr

, но, похоже, она не всегда быстрая.

Можно ли получить тот же вывод другим способом, но быстрее?

Ответы [ 6 ]

4 голосов
/ 21 марта 2019

Еще один перл, отображающий общие размеры, отсортированные по пользователю:

#!/usr/bin/perl
use warnings;
use strict;
use autodie;
use feature qw/say/;
use File::Spec;
use Fcntl qw/:mode/;

my $dir = shift;
my %users;

opendir(my $d, $dir);
while (my $file = readdir $d) {
  my $filename = File::Spec->catfile($dir, $file);
  my ($mode, $uid, $size) = (stat $filename)[2, 4, 7];
  $users{$uid} += $size if S_ISREG($mode);
}
closedir $d;

my @sizes = sort { $a->[0] cmp $b->[0] }
  map { [ getpwuid($_) // $_, $users{$_} ] } keys %users;
local $, = "\t";
say @$_ for @sizes;
2 голосов
/ 21 марта 2019

Получить список, добавить размеры и отсортировать его по владельцу (с Perl)

perl -wE'
    chdir (shift // "."); 
    for (glob ".* *") { 
        next if not -f;
        ($owner_id, $size) = (stat)[4,7]
            or do { warn "Trouble stat for: $_"; next };
        $rept{$owner_id} += $size 
    } 
    say (getpwuid($_)//$_, " => $rept{$_} bytes") for sort keys %rept
'

Я не смог сравниться с ним, и стоило бы попробовать его на подходегде каталог перебирается, в отличие от glob -ed (хотя я обнаружил, что glob гораздо быстрее в связанной с проблемой ).

Я ожидаю хорошего времени выполнения по сравнению с ls, что значительно замедляет , поскольку список файлов в одном каталоге становится длинным.Это связано с системой, поэтому на Perl это тоже повлияет, но, насколько я помню, она справляется с этим гораздо лучше.Однако я наблюдал резкое замедление только после того, как число записей достигло полумиллиона или около того, а не нескольких тысяч, поэтому я не уверен, почему это работает медленно в вашей системе.

Если это необходимо сделать рекурсивным, тоиспользуйте File :: Find .Например,

perl -MFile::Find -wE'
    $dir = shift // "."; 
    find( sub { 
        return if not -f;
        ($owner_id, $size) = (stat)[4,7] 
            or do { warn "Trouble stat for: $_"; return }; 
        $rept{$owner_id} += $size 
    }, $dir ); 
    say (getpwuid($_)//$_, "$_ => $rept{$_} bytes") for keys %rept
'

Это сканирует каталог с 2,4 ГБ, в основном небольшими файлами, по иерархии подкаталогов, за чуть более 2 секунд.du -sh заняло около 5 секунд (первый раунд).


Разумно объединить эти два в один скрипт

use warnings;
use strict;
use feature 'say';    
use File::Find;
use Getopt::Long;

my %rept;    
sub get_sizes {
    return if not -f; 
    my ($owner_id, $size) = (stat)[4,7] 
        or do { warn "Trouble stat for: $_"; return };
    $rept{$owner_id} += $size 
}

my ($dir, $recurse) = ('.', '');
GetOptions('recursive|r!' => \$recurse, 'directory|d=s' => \$dir)
    or die "Usage: $0 [--recursive] [--directory dirname]\n";

($recurse) 
    ? find( { wanted => \&get_sizes }, $dir )
    : find( { wanted => \&get_sizes, 
              preprocess => sub { return grep { -f } @_ } }, $dir );

say (getpwuid($_)//$_, " => $rept{$_} bytes") for keys %rept;

Я считаю, что это работает примерно так же, как код с одним dir-кодом выше, при запуске безрекурсивно (по умолчанию в существующем виде). ​​

Обратите внимание, что интерфейс File :: Find :: Rule имеет много удобств, но в некоторых важных случаях использования медленнее , что явноимеет значение здесь(Этот анализ должен быть переделан, так как ему несколько лет.)

2 голосов
/ 21 марта 2019

Синтаксический вывод ls - плохая идея.

Как насчет использования find вместо?

  • начало в каталоге ${dir}
    • ограничение до этого уровня каталога (-maxdepth 1)
    • ограничение на количество файлов (-type f)
    • вывод строки с именем пользователя и размером файла в байтах (-printf "%u %s\n")
  • запустить результаты через Perl-фильтр
    • разбить каждую строку (-a)
    • добавить в хеш под ключ (поле 0) размер (поле 1)
    • в конце (END {...}) распечатать содержимое хеша, отсортированное по ключу, то есть имя пользователя
$ find ${dir} -maxdepth 1 -type f -printf "%u %s\n" | \
     perl -ane '$s{$F[0]} += $F[1]; END { print "$_ $s{$_}\n" foreach (sort keys %s); }'
stefanb 263305714

Решение с использованием Perl:

#!/usr/bin/perl
use strict;
use warnings;
use autodie;

use File::Spec;

my %users;
foreach my $dir (@ARGV) {
    opendir(my $dh, $dir);

    # files in this directory
    while (my $entry = readdir($dh)) {
        my $file = File::Spec->catfile($dir, $entry);

        # only files
        if (-f $file) {
            my($uid, $size) = (stat($file))[4, 7];
            $users{$uid} += $size
        }
    }

    closedir($dh);
}

print "$_ $users{$_}\n" foreach (sort keys %users);

exit 0;

Тестовый прогон:

$ perl dummy.pl .
1000 263618544

Интересная разница. Решение Perl обнаруживает в моем тестовом каталоге еще 3 файла, чем решение find. Я должен подумать, почему это ...

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

Я видел какой-то awk в операторе?Вот один в GNU awk, использующий filefuncs расширение:

$ cat bar.awk
@load "filefuncs"
BEGIN {
    FS=":"                                     # passwd field sep
    passwd="/etc/passwd"                       # get usernames from passwd
    while ((getline < passwd)>0)
        users[$3]=$1
    close(passwd)                              # close passwd

    if(path="")                                # set path with -v path=...
        path="."                               # default path is cwd
    pathlist[1]=path                           # path from the command line
                                               # you could have several paths
    fts(pathlist,FTS_PHYSICAL,filedata)        # dont mind links (vs. FTS_LOGICAL)
    for(p in filedata)                         # p for paths
        for(f in filedata[p])                  # f for files
            if(filedata[p][f]["stat"]["type"]=="file")      # mind files only
                size[filedata[p][f]["stat"]["uid"]]+=filedata[p][f]["stat"]["size"]
    for(i in size)
        print (users[i]?users[i]:i),size[i]    # print username if found else uid
    exit
}

Пример выходных данных:

$ ls -l
total 3623
drwxr-xr-x 2 james james  3690496 Mar 21 21:32 100kfiles/
-rw-r--r-- 1 root  root         4 Mar 21 18:52 bar
-rw-r--r-- 1 james james      424 Mar 21 21:33 bar.awk
-rw-r--r-- 1 james james      546 Mar 21 21:19 bar.awk~
-rw-r--r-- 1 james james      315 Mar 21 19:14 foo.awk
-rw-r--r-- 1 james james      125 Mar 21 18:53 foo.awk~
$ awk -v path=. -f bar.awk
root 4
james 1410

Другой:

$ time awk -v path=100kfiles -f bar.awk
root 4
james 342439926

real    0m1.289s
user    0m0.852s
sys     0m0.440s

Еще одинтест с миллионом пустых файлов:

$ time awk -v path=../million_files -f bar.awk

real    0m5.057s
user    0m4.000s
sys     0m1.056s
1 голос
/ 21 марта 2019

Не уверен, почему вопрос помечается как perl, когда используется awk.

Вот простая версия Perl:

#!/usr/bin/perl

chdir($ARGV[0]) or die("Usage: $0 dir\n");

map {
    if ( ! m/^[.][.]?$/o ) {
        ($s,$u) = (stat)[7,4];
        $h{$u} += $s;
    }
} glob ".* *";

map {
    $s = $h{$_};
    $u = !( $s      >>10) ? ""
       : !(($s>>=10)>>10) ? "k"
       : !(($s>>=10)>>10) ? "M"
       : !(($s>>=10)>>10) ? "G"
       :   ($s>>=10)      ? "T"
       :                    undef
       ;
    printf "%-8s %12d\t%s\n", $s.$u, $h{$_}, getpwuid($_)//$_;
} keys %h;

  • glob получает наш список файлов
  • m// сбрасывает . и ..
  • stat размер и идентификатор
  • накапливаются размеры в %h
  • вычислить единицу с помощью сдвига битов (>>10 - целочисленное деление на 1024)
  • сопоставить UID с именем пользователя (// обеспечивает резерв)
  • результаты печати (без сортировки)
  • ПРИМЕЧАНИЕ: в отличие от некоторых других ответов, этот код не переходит в подкаталоги

Чтобы исключить символические ссылки, подкаталоги и т. Д., Измените if на соответствующие -X тесты. (например, (-f $_), (!-d $_ and !-l $_) и т. д.). См. perl docs по оптимизации файлового дескриптора _ для кэширования результатов статистики.

0 голосов
/ 22 марта 2019

Использование datamash Stefan Becker s find code ):

find ${dir} -maxdepth 1 -type f -printf "%u\t%s\n" | datamash -sg 1 sum 2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...