我有一个目录(例如/home/various/
)和许多子目录(例如,,/home/various/foo/
和/home/various/ber/
)。/home/various/kol/
/home/various/whatever/
有没有我可以运行的命令,它将细分每个文件扩展名的内容,显示总数,例如
- 总尺寸
- 文件数量
比方说,我不想在终端中手动输入每个文件扩展名,部分原因是我不知道(递归地)内部的所有文件扩展名/various/
。
像这样的输出会很棒:
*.txt 23 files, 10.2MB
*.pdf 8 files, 23.2MB
*.db 3 files, 2.3MB
*.cbz 24 files, 2.3GB
*.html 2,508 files, 43.9MB
*.readme 13 files, 4KB
答案1
基本代码
duext() {
case "$1" in
-* )
set "./$1"
esac
POSIXLY_CORRECT= find "${1-.}" -type f -exec du {} + | awk '
{
sz=$1
$1=""
sub("^ *","")
sub("^.*/","")
sub("^\\.","")
w=split($0,a,".")
e=tolower(w==1?"*":"*."a[w])
s[e]+=sz
n[e]+=1
}
END {
for (e in s) print 512*s[e]"\t"n[e]"\t"e
}'
}
用法:duext path
。默认path
为.
.该函数应该在sh
兼容的 shell 中运行。
该函数生成以下形式的线:
s<tab>n<tab>e
其中s
是使用的磁盘大小(以字节为单位),n
是文件数,e
是扩展名。这与您请求的输出不同,因为我决定优化解析。你所谓的“扩展名”只是 *nix 中文件名的一部分。文件名可能包含空格或制表符。将e
(可能包含空格或制表符)放在行尾使我们能够可靠地识别其他字段。例如,您可以轻松地按大小排序:
duext /home/various/ | sort -rn -k1,1 # optionally: … | column -t
笔记:
- 路径名中的换行符将使结果不正确。
POSIXLY_CORRECT= du …
是一种获取已用磁盘大小的便携式方法。它以 512 字节为单位进行报告,因此512*s[e]
位于awk
代码的后面。 GNUdu
提供了一些有趣的选项(例如--apparent-size
);他们可能需要调整awk
代码。sub("^\\.","")
负责不将名称中的前导点视为扩展分隔符。实际上.nfo
被解释为不带扩展名的(隐藏)文件,而不是带扩展名的文件nfo
。如果这不是您想要的,请删除该行。- 该代码区分空扩展名(例如
foo.
)和无扩展名(foo
)。前者据报道为*.
;后者被报道为*
。 - 该代码不区分大小写。删除
tolower
以使其区分大小写。 - 硬链接可能会扭曲结果。
du
如果文件是指向某些已记入文件的硬链接,则您可能会也可能不会忽略该文件。另外,根据需要find … -exec du {} +
运行du
多次(以避免argument list too long
),并且硬链接文件可能会也可能不会传递到同一个du
.您可以通过使用du -l
(GNU 中的不可移植选项du
)或通过du
每个文件运行一个来强制计算每个硬链接:find … -exec du {} \;
。为了可靠地对硬链接进行一次计数,您需要一种不同的方法(GNU 的单个实例du
和--files0-from=
?)。一般来说,可以有不同扩展名的硬链接。当您想要单独计算每个硬链接时,这不是问题,但如果您想将它们计算为一个文件,那么分配哪个扩展名是不确定的。
自定义格式
我不确定MB
你的意思是不是兆字节或兆字节,我认为是后者。以下代码应转换为您想要的格式:
yourformat() { awk '
function human(x) {
if (x<1000) {return x} else {x/=1000}
s="kMGTEPZY";
while (x>=1000 && length(s)>1)
{x/=1000; s=substr(s,2)}
return int(10*x+0.5)/10 substr(s,1,1)
}
{
s=$1; n=$2
$1=""; $2=""
sub("^ ","")
print $0" "n" file"(n==1?"":"s")", "human(s)"B"
}'
}
(注:human(x)
摘自这个答案并进行了调整。)
像这样使用它:
duext /home/various/ | yourformat
duext
在内部使用awk
,现在我们将其通过管道传输到yourformat
也使用awk
.总的来说,我们可以awk
在单个函数中使用 single 来代替。仍然单独的 awk
s 允许我们将 eg 放在sort …
两者之间(在单个 shell 函数中或在函数之间的管道中)。虽然可以在awk
(或至少在 GNU 中)实现某种排序awk
,但重新发明轮子是没有意义的。 IMO 保持第一个易于解析的输出awk
是正确的事情。这样你就可以申请任何稍后进行过滤和格式化。
让我们改进一下你的格式,这样就column -t
可以使用了。 1024 的因数怎么样?
myformat() { awk '
function human(x) {
if (x<1000) {return x" "} else {x/=1024}
s="kMGTEPZY";
while (x>=1000 && length(s)>1)
{x/=1024; s=substr(s,2)}
return int(10*x+0.5)/10" "substr(s,1,1)"i"
}
{
s=$1; n=$2
$1=""; $2=""
sub("^ ","")
print $0"\t"n" file"(n==1?"":"s")"\t"human(s)"B"
}'
}
进而:
duext /home/various/ | sort -nr -k1,1 | myformat | column -t -s "$(printf '\t')"
笔记:
"$(printf '\t')"
是一种获取制表符的便携式方法。在某些 shell 中(例如在 Bash 中)$'\t'
执行相同的操作。column
本身是不可移植的。- 带有制表符的扩展名会破坏格式。但它们相当罕见。
坦白说,我很喜欢这个解决方案,所以保留它。我创建了一个名为due
供将来使用的脚本:
#!/bin/sh
duext() {
…
}
myformat {
…
}
duext "${1-.}" | sort -nr -k1,1 | myformat | column -t -s "$(printf '\t')"
答案2
这是一个非常有趣的问题,我能构建的最好的就是这个脚本:
set -e
# set -x
folder=$1
counter=$(tempfile)
# List file extensions
list_extensions() {
find "$folder" -type f |
while read filename
do
basename=${filename##*/}
ext=${basename##*.}
echo ${ext,,} # downcase extensions to prevent duplicates
done |
sort -u
}
list_extensions |
while read extension
do
size=$(find "$folder" -type f -iname "*.$extension" -fprintf $counter . -print0 |
du -hc --files0-from=- | tail -n 1 | sed -E 's/\s+total//')
count=$( wc -c < $counter )
printf "*.%-10s\t%6s files\t%10s\n" "$extension" "$count" "$size"
done
rm $counter
它不支持复杂的文件名,可能会有很多异常,性能也不是很好,但它确实有效。
示例输出:
*.wma 122 files 411M
*.wpl 16 files 64K
*.xls 2 files 24K
*.xlsx 1 files 28K
*.zip 5 files 333M