在Linux中,如何查找文件名与正则表达式匹配的文件的磁盘使用情况?

51

因此,在许多情况下,我希望知道我的硬盘空间中有多少被使用,以便我知道要清除哪些文件,将其转换为其他格式,存储在其他位置(例如数据DVD),移动到另一个分区等。在这种情况下,我正在从SliTaz Linux引导介质中查看Windows分区。

在大多数情况下,我想要的是文件和文件夹大小,为此我使用基于NCurses的ncdu

                ncdu

但在这种情况下,我想要一种获取匹配正则表达式的所有文件大小的方法。一个.bak文件的示例正则表达式:

.*\.bak$

在标准的Linux系统中,或使用 BusyBox,我该如何获取这些信息?

编辑:输出应该是可被脚本解析的。

6个回答

58
我建议使用以下命令:find . -regex '.*\.bak' -print0 | du --files0-from=- -ch | tail -1 一些注意事项:
- find-print0 选项和 du--files0-from 选项可避免文件名中的空格问题 - 正则表达式匹配整个路径,例如./dir1/subdir2/file.bak,而不仅仅是file.bak,因此如果修改它,请考虑这一点。 - 我在 du 中使用了 h 标志来产生“人类可读”的格式,但是如果你想解析输出,你可能最好使用 k(始终使用千字节)。 - 如果删除 tail 命令,您将另外看到特定文件和目录的大小。
附注:一个很好的用于查找磁盘空间占用情况的 GUI 工具是 FileLight。它不能使用正则表达式,但非常方便地查找占用磁盘的大型目录或文件。

3
+1,看起来很酷!那加上 -s 参数的 du 呢?我现在没法确认,不过我记得 du 可以显示总大小而不需要用到 tail 呢。FileLight 工具让我想起了 Gnome 的磁盘使用分析器。但是,我觉得我在原帖中提到的 ncdu 应用程序的“详细视图”界面更直观一些,不过多样性也是好事 :) (我已经打开了磁盘使用分析器,让某个人从那个时髦的用户界面认为我为他修复电脑所做的工作比实际要复杂... 这招真的有效!呵呵)。 - Camilo Martin
1
“-s” 显示每个参数的总计,由于我们有多个参数,因此我们需要使用“-c”选项。 - Michał Kosmulski
谢谢,已经检查过并且运行正常(但是无法使用BusyBox的du,因为它不支持--files0-from,所以我安装了coreutils),所以我会接受这个答案,因为它似乎对恐怖分子文件名具有免疫力。 - Camilo Martin
我遇到了一个文件名过长的错误(我筛选了100k或更多的文件)。 - basZero
2
这里有一个缺陷。如所示的查找将包括目录。然后,du将为目录和目录中的文件总计。嵌套目录将被多次计算。我建议在查找选择中使用“-type f”。 - Steven the Easily Amused
这太棒了。在 macOS 上,执行 brew install coreutils 命令以获取 finddu 的 GNU 变体后,该命令看起来像这样:gfind $HOME/ons -type f -regex '.*pre-commit-config.yaml' -print0 | gdu --files0-from=- -cb | sort -n。这将在原始命令中添加一个排序,并输出文件大小(以字节为单位)以允许排序工作。 - Ashutosh Jindal

30

du 是我最喜欢的答案。如果你有一个固定的文件系统结构,你可以使用:

du -hc *.bak

如果您需要添加子目录,只需添加:

du -hc *.bak **/*.bak **/**/*.bak

然而,这并不是一个非常有用的命令,所以使用你的查找:

TOTAL=0;for I in $(find . -name \*.bak); do  TOTAL=$((TOTAL+$(du $I | awk '{print $1}'))); done; echo $TOTAL

这将回显您找到的所有文件的总大小(以字节为单位)。

希望对您有所帮助。


此内容不支持正则表达式。 - Felipe Alvarez
它有我在另一个答案中指出的相同问题。由于目录可以命名为“*.bak”,du将同时计算名为.bak的目录中的文件以及整个目录本身!这将导致过多计数和重复计数(或更糟,如果您有嵌套的.bak目录)。 - Steven the Easily Amused
我刚刚添加了“-s”以包含子文件夹。 - cjbarth

3
在Bourne Shell中运行以下命令,声明一个函数以计算当前目录中所有与正则表达式模式匹配的文件大小之和:
sizeofregex() { IFS=$'\n'; for x in $(find . -regex "$1" 2> /dev/null); do du -sk "$x" | cut -f1; done | awk '{s+=$1} END {print s}' | sed 's/^$/0/'; unset IFS; }

(或者,您可以将其放在脚本中。)

用法:

cd /where/to/look
sizeofregex 'myregex'

结果将是一个数字(以KiB为单位),包括0(如果没有与您的正则表达式匹配的文件)。
如果您不希望它查找其他文件系统(比如您想在/下查找所有.so文件,它是/dev/sda1的挂载点,但不在/home下,它是/dev/sdb1的挂载点),请在上面的函数中添加-xdev参数到find

即使设置了IFS,仍然相当hackish。使用find-exec有什么问题吗? - jordanm
@jordanm 我一直使用 IFS=$'\n' 来读取列表,所以我已经习惯了它 :P 但是你说 awk 可以做到所有这些 - 我只是涉及到了 awk 的表面,如果你能发布一个用 awk 实现的方法,并且它不那么 hacky,我会接受它 :) 我只是想要一个可行的解决方案,并花了一些时间来编写该函数,所以我认为我应该分享它。它对我来说运行得足够快,但如果有更好的方法,我完全支持。如果不是脚本,每个硬盘驱动器大约需要 1 分钟的时间确实太慢了。 - Camilo Martin
1
你在这里做的事情是不好的,因为你忘记了UNIX上的文件名可能包含换行符。唯一不允许的字符是 '\0'。建议阅读:http://mywiki.wooledge.org/ParsingLs(它是关于“ls”的,但不要被它迷惑了:你会陷入同样的陷阱)。 - Daniel Kamil Kozar
1
du -sk build/ bin/ | awk '{s+=$1} END { if (s ~ /[0-9]+/) { print s; } else print "0"; }'。awk通常可以完成cut的工作,但在您的情况下,cut也不需要。 - jordanm
我确实知道Unix对文件名中换行符的支持存在问题(这真的很不幸),但只有恐怖分子才会在文件名中加入换行符(没有考虑到我自己正在检查Windows分区,尽管NTFS本身允许这样做,但Windows不会)。除此之外,感谢提醒和漂亮的代码片段,但它只是获取几个文件夹大小的总和。如果您知道一种考虑正则表达式思想的方法,并将其发布为答案,我会接受它 :) - Camilo Martin
显示剩余2条评论

3

之前的方法对我来说并没有很好地解决问题(我在使用du命令时遇到了困难),但是下面这种方法十分有效:

find path/to/directory -iregex ".*\.bak$" -exec du -csh '{}' + | tail -1
iregex选项是一个不区分大小写的正则表达式。如果您希望区分大小写,请使用regex
如果您对正则表达式不熟悉,可以使用inamename标志(前者不区分大小写):
find path/to/directory -iname "*.bak" -exec du -csh '{}' + | tail -1

如果您想要每个匹配项的大小(而不仅仅是总和),只需省略管道尾部命令即可:
find path/to/directory -iname "*.bak" -exec du -csh '{}' +

这些方法避免了@MaddHackers回答中的子目录问题。
希望这能帮助其他处于同样情况下的人(在我的情况下,查找.NET解决方案中所有DLL的大小)。

1
需要注意的是,+ 的含义是 find 尽可能将尽可能多的结果附加到单个 du 调用中,以便尽可能少地调用 du 命令,但由于系统限制(例如允许的最大参数数量),可能无法将所有结果附加到单个 du 调用中,此时将它们分成多个调用,这将导致结果不正确。 - Mecki
1
哦,你忘记引用 *.bak 了。在你的示例中,shell会展开它,但你想让 find 展开它,所以你必须使用 "*.bak"。我会为你修复这个问题。 - Mecki

1
接受的回复建议使用


find . -regex '.*\.bak' -print0 | du --files0-from=- -ch | tail -1

但是在我的系统上这并不起作用,因为du 不知道 --files-0-from 选项。只有GNU du 知道该选项,它既不属于POSIX标准(因此您在FreeBSD或macOS中找不到它),也不会在基于BusyBox的Linux系统(例如大多数嵌入式Linux系统)或任何未使用GNU du 版本的其他Linux系统中找到它。
然后有一个回复建议使用:
find path/to/directory -iregex .*\.bak$ -exec du -csh '{}' + | tail -1

这个解决方案只适用于找到的文件不太多的情况,因为 + 意味着 find 将尝试在单个调用中使用尽可能多的 hits 调用 du,但是,系统可能有最大参数数目 (N),如果 hits 数量超过此值,则 find 将多次调用 du,将 hits 分成每组小于或等于 N 个项目,此情况下结果将是错误的,并且只显示最后一个 du 调用的大小。

最后有一种使用 statawk 的方法,这是一种不错的方法,但它依赖于 shell globbing,只有 Bash 4.x 或更高版本支持。它不会在旧版本中工作,如果它与其他 shell 工作,则是不可预测的。

一种符合 POSIX 标准的解决方案(适用于 Linux、macOS 和任何 BSD 变体),不受任何限制,肯定可以在每个 shell 中使用:

find . -regex '.*\.bak' -exec stat -f "%z" {} \; | awk '{s += $1} END {print s}'

这是一篇很棒的文章,点赞!关于参数计数限制的发现尤其重要,因为它可能会导致错误的结果,让人抓狂直到他找出问题所在。 - Camilo Martin

1
如果您熟悉全局模式并且只对当前目录感兴趣,那么这个问题就很简单了:
stat -c "%s" *.bak | awk '{sum += $1} END {print sum}'

或者

sum=0
while read size; do (( sum += size )); done < <(stat -c "%s" *.bak)
echo $sum

%s指令给出的是字节而不是千字节。

如果你想要进入子目录,使用bash 4版本,你可以使用模式**/*.bak并且输入shopt -s globstar


所以,在Bash 4中,**/*.bak表示位于任何子目录中的.bak文件?也就是说,不仅仅是在一个目录下吗? - Camilo Martin
很遗憾,这在我的Bash 4.2中无法工作。请查看此截图(http://i.stack.imgur.com/eRWaH.png)。它只能进入一个子文件夹,就好像`**/*.ext`是`*/*.ext`一样。 - Camilo Martin
@CamiloMartin,你是否执行了 shopt -s globstar 命令?尝试使用 echo $BASH_VERSION 查看当前 shell 的版本。这对我有效:mkdir -p a/b/c/d; touch a/b/c/d/file.txt; ls **/*txt - glenn jackman
@glennjackman echo $BASH_VERSION 给我返回了 4.2.0(2)-release。按照你的建议,执行了 shopt -s globstar 命令后,在小文件夹结构中可以正常工作,但是如果在 / 目录下尝试,CPU 使用率会达到 100%,几分钟后 bash 就会被杀死。我不知道为什么,也许是因为它是在只有 256MB RAM 的 VM 上运行(虽然在这个轻量级发行版上可以浏览网页等),但仍然似乎不太可靠。 - Camilo Martin
@CamiloMartin,它可能没有find实现得那么高效,但是你真的在爬取整个文件系统中的文件吗? - glenn jackman
@glennjackman 不是这个文件系统的 /,而是另一个文件系统的 /,所以是的,如果在分区的根目录中使用它,我需要它不会崩溃或内存泄漏。顺便说一下,我一直觉得 find 比 Windows 的(未索引的)搜索功能好太多了...我不明白为什么 Linux 扫描 NTFS 驱动器比 Windows 更快。而且还有正则表达式! - Camilo Martin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接