目录中按所有者汇总文件大小的最快方法

时间:2019-03-21 15:13:31

标签: linux shell perl

我正在使用以下命令,并使用别名来按目录中的所有者打印所有文件大小的总和

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 个答案:

答案 0 :(得分:4)

另一个perl,显示按用户排序的总大小:

#!/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;

答案 1 :(得分:2)

解析ls的输出-不好的主意。

如何改用find

  • 从目录${dir}开始
    • 限制为该目录级别(-maxdepth 1
    • 文件限制(-type f
    • 打印一行用户名和文件大小以字节为单位(-printf "%u %s\n"
  • 通过perl过滤器运行结果
    • 分割每行(-a
    • 将大小(字段1)添加到键(字段0)下的哈希中
    • 最后(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

有趣的差异。与find解决方案相比,Perl解决方案在我的测试目录中发现了3个文件。我必须考虑为什么会这样...

答案 2 :(得分:2)

从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(我发现globrelated problem中更快)。

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秒多一点的时间内扫描一个2.4 Gb的目录,该目录中大部分子文件位于子目录的层次结构中。 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;

当以非递归方式运行时(默认情况下),我发现它的执行效果与上面的仅一个目录的代码相同。

请注意,File::Find::Rule界面有很多便利,但是在某些重要的用例中却是slower,在这里显然很重要。 (该分析已经使用了几年,应该重新进行。)

答案 3 :(得分:1)

不确定使用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的大小和uid
  • %h中累积大小
  • 通过位移来计算单位(>>10是1024除以整数)
  • 将uid映射到用户名(//提供后备功能)
  • 打印结果(未排序)
  • 注意::与其他答案不同,此代码不会递归到子目录中

要排除符号链接,子目录等,请将if更改为适当的-X测试。 (例如(-f $_)(!-d $_ and !-l $_)等)。请参见_文件句柄优化中的perl docs,以缓存统计信息结果。

答案 4 :(得分:1)

我在操作中看到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

答案 5 :(得分:0)

使用datamash(和Stefan Becker's find code):

find ${dir} -maxdepth 1 -type f -printf "%u\t%s\n" | datamash -sg 1 sum 2