测量每个目录的特定文件类型的磁盘使用情况(递归地,作为“du --include”的演示)

mad*_*joe 7 performance bash find gnu disk-usage

这是我的工作代码,但我相信它没有优化 - 必须有一种方法可以比这更快地完成工作:

find . -type f -iname '*.py' -printf '%h\0' |
  sort -z -u |
  xargs -r -0 -I{} sh -c '
    find "{}" -maxdepth 1 -type f -iname "*.py" -print0 |
      xargs -r -0 du -sch |
      tail -1 |
      cut -f1 |
      tr "\n" " "
    echo -e "{}"' |
  sort -k1 -hr |
  head -50
Run Code Online (Sandbox Code Playgroud)

目标是递归搜索所有包含目录的目录,*.py然后*.py按每个目录的名称打印所有文件的总大小,按大小按相反顺序对它们进行排序并仅显示前 50 个。

任何想法如何改进此代码(性能方面)但保持相同的输出?

编辑:

我在以下示例中测试了您的建议:47GB total: 5805 files 不幸的是,我无法逐一比较,因为并非所有建议都遵循相同的准则:总大小应该是磁盘使用量,分隔符应该只是一个空格。格式应如下所示:numfmt --to=iec-i --suffix=B

以下 4 个是排序输出,但 David 显示文件的累积大小,而不是实际磁盘使用情况。然而,他的进步是显着的:快了 9.5 倍以上。Stéphane 和 Isaac 的代码是非常小的赢家,因为他们的代码比参考代码快大约 32 倍。

$ time madjoe.sh
real    0m2,752s
user    0m3,022s
sys     0m0,785s

$ time david.sh 
real    0m0,289s
user    0m0,206s
sys     0m0,131s

$ time isaac.sh 
real    0m0,087s
user    0m0,032s
sys     0m0,032s

$ time stephane.sh 
real    0m0,086s
user    0m0,013s
sys     0m0,047s
Run Code Online (Sandbox Code Playgroud)

不幸的是,下面的代码既没有排序也没有显示最大的 50 个结果(此外,在之前与 Isaac 的代码比较期间,下面的代码比 Isaac 的改进慢了大约 6 倍):

$ time hauke.sh 
real    0m0,567s
user    0m0,609s
sys     0m0,122s
Run Code Online (Sandbox Code Playgroud)

ImH*_*ere 7

通过在一个数组中收集所有目录总和并在最后打印(使用 GNU awk)来简化来自 @HaukeLaging 的解决方案。此外,只numfmt需要调用一次(最后)。

#!/bin/sh

find . -type f -iname '*.py' -printf '%s %h\0' |
    awk 'BEGIN { RS="\0"; };

         { gsub(/\\/,"&&"); gsub(/\n/,"\\n");
           size=$1; sub("[^ ]* ",""); dirsize[$0]+=size }

         END {   PROCINFO["sorted_in"] = "@val_num_desc";
                 i=0;
                 for ( dir in dirsize ) { if(++i<=50) 
                     { print dirsize[dir], dir; }else{ exit } 
                 }
             }        ' | numfmt --to=iec-i --suffix=B
Run Code Online (Sandbox Code Playgroud)

这会生成 py 文件的累积表观大小(而不是它们的磁盘使用情况),并避免在目录的子目录中汇总文件。


Sté*_*las 6

要计算磁盘使用量而不是表观大小的总和,您需要使用%b¹ 而不是%s并确保每个文件只计算一次,例如:

LC_ALL=C find . -iname '*.py' -type f -printf '%D:%i\0%b\0%h\0' |
  gawk -v 'RS=\0' -v OFS='\t' -v max=50 '
    {
      inum = $0
      getline du
      getline dir
    }
    ! seen[inum]++ {
      gsub(/\\/, "&&", dir)
      gsub(/\n/, "\\n", dir)
      sum[dir] += du
    }
    END {
      n = 0
      PROCINFO["sorted_in"] = "@val_num_desc"
      for (dir in sum) {
        print sum[dir] * 512, dir
        if (++n >= max) break
      }
    }' | numfmt --to=iec-i --suffix=B --delimiter=$'\t'
Run Code Online (Sandbox Code Playgroud)

目录名称\n中的换行符呈现为,反斜杠(至少在当前语言环境中解码的那些)呈现为\\

如果在多个目录中找到文件,则将其与在其中找到的第一个目录进行计数(顺序不确定)。

它假设POSIXLY_CORRECT环境中没有变量(如果有,则设置PROCINFO["sorted_in"]无效,gawk因此不会对列表进行排序)。如果你不能保证it³,你可以随时开始gawkenv -u POSIXLY_CORRECT gawk ...(假设GNUenv或兼容;或(unset -v POSIXLT_CORRECT; gawk ...))。

您的方法还有一些其他问题:

  • 如果没有LC_ALL=C,GNUfind不会报告名称在语言环境中不构成有效字符的文件,因此您可能会错过一些文件。
  • 嵌入{}代码sh构成任意代码注入漏洞。想想一个名为$(reboot).py. 你永远不应该这样做,文件的路径应该作为额外的参数传递,并使用位置参数在代码中引用。
  • echo不能用于显示任意数据(尤其是-e在这里没有意义的数据)。使用printf来代替。
  • 如果文件列表很大xargs -r0 du -schdu可以使用,多次调用,在这种情况下,最后一行将只包含上次运行的总数。

¹%b以 512 字节为单位报告磁盘使用情况。512 字节是磁盘分配的最小粒度,因为这是传统扇区的大小。还有%kwhich is int(%b / 2),但这会在具有 512 字节块的文件系统上给出不正确的结果(文件系统块通常是 2 的幂,并且至少有 512 字节大)

² 也使用LC_ALL=Cfor gawk 会使其更高效,但可能会使用 BIG5 或 GB18030 字符集(并且文件名也以该字符集编码)在语言环境中破坏输出,因为反斜杠的编码也可以在编码中找到那里的其他一些字符。

³ 请注意,如果您的shis bash,在脚本中POSIXLY_CORRECT设置为,并且如果以或开头,则会将其导出到环境中,因此该变量也可能会无意中进入。yshsh-a-o allexport