按文件扩展名分解目录并获取每个目录的总大小和计数

按文件扩展名分解目录并获取每个目录的总大小和计数

我有一个目录(例如/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 来代替。仍然单独的 awks 允许我们将 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

相关内容