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)
通过在一个数组中收集所有目录总和并在最后打印(使用 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 文件的累积表观大小(而不是它们的磁盘使用情况),并避免在目录的子目录中汇总文件。
要计算磁盘使用量而不是表观大小的总和,您需要使用%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³,你可以随时开始gawk为env -u POSIXLY_CORRECT gawk ...(假设GNUenv或兼容;或(unset -v POSIXLT_CORRECT; gawk ...))。
您的方法还有一些其他问题:
LC_ALL=C,GNUfind不会报告名称在语言环境中不构成有效字符的文件,因此您可能会错过一些文件。{}代码sh构成任意代码注入漏洞。想想一个名为$(reboot).py. 你永远不应该这样做,文件的路径应该作为额外的参数传递,并使用位置参数在代码中引用。echo不能用于显示任意数据(尤其是-e在这里没有意义的数据)。使用printf来代替。xargs -r0 du -sch,du可以使用,多次调用,在这种情况下,最后一行将只包含上次运行的总数。¹%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