这是我的工作代码,但我相信它没有优化 - 必须有一种方法可以比这更快地完成工作:
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
目标是递归搜索包含的所有目录,*.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
遗憾的是,以下代码既不排序也不显示最大 50 个结果(此外,在之前与 Isaac 的代码进行比较时,以下代码比 Isaac 的改进慢约 6 倍):
$ time hauke.sh
real 0m0,567s
user 0m0,609s
sys 0m0,122s
答案1
通过将所有目录总和收集在一个数组中并在最后全部打印(使用 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
这会生成 py 文件的累积表观大小(而不是其磁盘使用情况),并避免对目录的子目录中的文件进行求和。
答案2
要计算磁盘使用情况而不是表观大小的总和,您需要使用%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'
目录名称中的换行符呈现为\n
,反斜杠(至少在当前语言环境中解码的反斜杠)呈现为\\
。
如果在多个目录中找到一个文件,则该文件将根据第一个找到的目录进行计数(顺序不确定)。
POSIXLY_CORRECT
它假设环境中没有变量(如果有,设置PROCINFO["sorted_in"]
无效,gawk
因此列表不会被排序)。如果您不能保证这一点,您始终可以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
1%b
以 512 字节为单位报告磁盘使用情况。 512 字节是磁盘分配的最小粒度,因为这是传统扇区的大小。还有%k
which is int(%b / 2)
,但这会在具有 512 字节块的文件系统上给出不正确的结果(文件系统块通常是 2 的幂且至少为 512 字节大)
²LC_ALL=C
也用于 gawk 会使其效率更高一些,但可能会破坏使用 BIG5 或 GB18030 字符集的语言环境中的输出(并且文件名也以该字符集进行编码),因为反斜杠的编码也在编码中找到那里还有其他一些角色。
³ 请注意,如果您的sh
is bash
,在脚本中POSIXLY_CORRECT
设置为,并且如果以或开头,则将其导出到环境中,因此该变量也可能会无意中潜入。y
sh
sh
-a
-o allexport
答案3
我怀疑你需要写自己的 du。
目前,您使用两个 find 和一个 du 对层次结构进行三次递归。
我建议从 perl 的File::Find
包开始。
或者,您的第一个查找可能会输出类似的内容-printf '%k %h\n'
,然后您可以按目录排序,使用 perl 或 awk (甚至 bash)来总计目录并转换为“人类”可读,最后排序和头。
无论哪种方式,您都应该 A) 仅遍历目录树一次,B) 创建尽可能少的进程。
编辑:示例实现
#!/bin/bash
find . -type f -iname '*.py' -printf '%k %h\n' | sort -k2 | (
at=
bt=
output() {
if [[ -n "$at" ]]
then
printf '%s\t%s\n' "$at" "$bt"
fi
}
while read a b
do
if [[ "$b" != "$bt" ]]
then
output
bt="$b"
at=0
fi
at=$(( $at + $a ))
done
output
) | sort -hr | head -50 | numfmt -d' ' --field=1 --from-unit=Ki --to=iec-i
注意:%k 很重要。 %s 报告表观大小,而 %k(和du
)报告磁盘大小。它们对于稀疏文件和大文件有所不同。 (如果你愿意du --apparent-size
,那就这样吧。)
注意:numfmt 应该放在最后,因此它会运行一次。使用“%k”时,需要指定起始单位。
注意:numfmt 的 -d 参数应包含单个选项卡。我无法在这里输入,并且 numfmt 不会接受-d'\t'
。如果分隔符不是制表符,则间距会变得混乱。因此我在主体中使用了 printf 而不是 echo 。 (另一种方法是使用 echo 和最后的 sed 将第一个空格更改为制表符。
注意:我最初错过了第一次排序,并在重新测试中得到了某些目录的重复条目。
注意:numfmt 是最近才出现的。
答案4
这可能会快很多,但它不完全等同于你的方法吗?它不会对子目录文件进行两次计数:
find . -type f -iname '*.py' -printf '%s %h\0' |
awk 'BEGIN { RS="\0"; }; '\
'{ pos=index($0," "); size=substr($0,1,(pos-1)); dir=substr($0,pos+1); gsub("\n","\\n",dir); '\
'if(dir!=lastdir) { if(NR>1) { "numfmt --to=iec-i --suffix=B " sizesum " | tr -d \"\n\"" | getline fsize; print fsize " " lastdir; } '\
'sizesum=size; lastdir=dir; } '\
'else sizesum=sizesum+size; }; '\
'END { "numfmt --to=iec-i --suffix=B " sizesum " | tr -d \"\n\"" | getline fsize; print fsize " " lastdir; }'
3,2KiB ./dir1
1,1MiB ./dir2
除了速度更快之外,它还用文字替换换行符\n
。如果您期望目录名称带有换行符,那么您必须处理它们,直到管道结束,这是您的代码不执行的操作。