我想知道在大型复杂目录结构中有多少常规文件具有扩展名.c
,以及这些文件分布在多少目录中。我想要的输出就是这两个数字。
我已经看过这个关于如何获取文件数量的问题,但我也需要知道文件所在的目录数量。
- 我的文件名(包括目录)可能有任何字符;它们可能以
.
or开头,-
并带有空格或换行符。 - 我可能有一些名称以 , 结尾的
.c
符号链接,以及指向目录的符号链接。我不希望符号链接被跟踪或计算,或者我至少想知道它们是否以及何时被计算。 - 目录结构有很多层级,顶层目录(工作目录)中至少有一个
.c
文件。
我匆忙在(Bash)shell中写了一些命令来自己计算它们,但我认为结果不准确......
shopt -s dotglob
shopt -s globstar
mkdir out
for d in **/; do
find "$d" -maxdepth 1 -type f -name "*.c" >> out/$(basename "$d")
done
ls -1Aq out | wc -l
cat out/* | wc -l
这会输出关于不明确的重定向、丢失当前目录中的文件以及遇到特殊字符(例如,重定向find
输出在 filenames 中打印换行符)的抱怨,并写入一大堆空文件(oops)。
如何可靠地枚举我的.c
文件及其包含的目录?
如果有帮助,这里有一些命令可以创建带有错误名称和符号链接的测试结构:
mkdir -p cfiles/{1..3}/{a..b} && cd cfiles
mkdir space\ d
touch -- i.c -.c bad\ .c 'terrible
.c' not-c .hidden.c
for d in space\ d 1 2 2/{a..b} 3/b; do cp -t "$d" -- *.c; done
ln -s 2 dirlink
ln -s 3/b/i.c filelink.c
在生成的结构中,7 个目录包含.c
文件,29 个常规文件以(如果在运行命令时关闭)结尾.c
(dotglob
如果我算错了,请告诉我)。这些是我想要的数字。
请随意不要使用此特定测试。
注意:任何 shell 或其他语言的答案都会经过我的测试和赞赏。如果我必须安装新软件包,没问题。如果您知道 GUI 解决方案,我鼓励您分享(但我可能不会安装整个 DE 来测试它):) 我使用 Ubuntu MATE 17.10。
我没有使用符号链接检查输出,但是:
find
命令打印.c
它找到的每个文件的目录名称。sort | uniq -c
将告诉我们每个目录中有多少文件(sort
这里可能不需要,不确定)sed
,我用 替换目录名称1
,从而消除所有可能的奇怪字符,只有计数和1
剩余tr
d
此处与 基本相同NR
。我本可以省略1
在sed
命令中插入,只是在这里打印NR
,但我认为这更清楚一些。在 之前
tr
,数据是 NUL 分隔的,对所有有效文件名都是安全的。使用 zsh 和 bash,您可以使用
printf %q
带引号的字符串,其中不包含换行符。因此,您可能可以执行以下操作:但是,即使
**
不应该为指向目录的符号链接扩展,我也无法在 bash 4.4.18(1) (Ubuntu 16.04) 上获得所需的输出。但是 zsh 工作正常,命令可以简化:
D
使这个 glob 能够选择点文件,.
选择常规文件(所以,不是符号链接),并且:h
只打印目录路径而不是文件名(如find
's%h
)(参见文件名生成和修饰符部分)。所以使用 awk 命令我们只需要计算出现的唯一目录的数量,行数就是文件数。Python 具有
os.walk
,即使在面对奇怪的文件名(例如包含换行符的文件名)时,也可以使此类任务变得简单、直观且自动稳健。我最初在 chat中发布的这个 Python 3 脚本旨在在当前目录中运行(但它不必位于当前目录中,您可以更改它传递到的路径os.walk
):这将打印直接包含至少一个名称以 结尾的文件的目录计数
.c
,后跟一个空格,然后是名称以 结尾的文件的计数.c
。“隐藏”文件——即名称以 -- 开头的文件.
被包括在内,隐藏目录也被类似地遍历。os.walk
递归遍历目录层次结构。它枚举从您给它的起点递归访问的所有目录,将有关每个目录的信息作为三个值的元组生成,root, dirs, files
. 对于它遍历到的每个目录(包括您为其命名的第一个目录):root
保存该目录的路径名。请注意,这与系统的“根目录”完全无关(/
也与. 在这种情况下,从路径开始——即,当前目录——然后到它下面的任何地方。/root
root
.
dirs
包含目录的所有子目录的路径名列表,其名称当前保存在root
.files
包含所有文件的路径名列表,这些文件位于当前保存名称root
但本身不是目录的目录中。请注意,这包括除常规文件之外的其他类型的文件,包括符号链接,但听起来您不希望任何此类条目以结尾.c
并且有兴趣看到任何这样的条目。在这种情况下,我只需要检查元组的第三个元素
files
(我fs
在脚本中调用它)。像find
命令一样,Pythonos.walk
为我遍历子目录;我唯一需要检查自己的是每个文件包含的文件的名称。find
但是,与命令不同的是,它会os.walk
自动为我提供这些文件名的列表。该脚本不遵循符号链接。您很可能不希望此类操作遵循符号链接,因为它们可能形成循环,并且因为即使没有循环,如果可以通过不同的符号链接访问相同的文件和目录,它们也可能会被多次遍历和计数。
如果您确实想要
os.walk
遵循符号链接(通常不会这样做),那么您可以传递followlinks=true
给它。也就是说,os.walk('.')
你可以写而不是写os.walk('.', followlinks=true)
。我重申,您很少需要这样做,特别是对于像这样的任务,您递归地枚举整个目录结构,无论它有多大,并计算其中满足某些要求的所有文件。查找 + Perl:
解释
该
find
命令将查找任何常规文件(因此没有符号链接或目录),然后打印它们所在的目录名称 (%h
) 后跟\0
.perl -0 -ne
: 逐行读取输入 (-n
) 并将给出的脚本-e
应用于每一行。将-0
输入行分隔符设置为,\0
以便我们可以读取以空值分隔的输入。$k{$_}++
:$_
是一个特殊的变量,它取当前行的值。这用作hash%k
的键,其值是每个输入行(目录名称)被看到的次数。}{
: 这是一种简写方式END{}
。之后的任何命令}{
都将在处理完所有输入后执行一次。print scalar keys %k, " $.\n"
:keys %k
返回散列中键的数组%k
。scalar keys %k
给出该数组中的元素数,即看到的目录数. 这与 的当前值一起打印,这是$.
一个保存当前输入行号的特殊变量。由于这是在最后运行,因此当前输入的行号将是最后一行的编号,因此是到目前为止看到的行数。为了清楚起见,您可以将 perl 命令扩展为:
这是我的建议:
这个简短的脚本创建一个临时文件,查找当前目录中和下的每个文件,并将
.c
列表写入临时文件。grep
然后用于对文件进行计数(按照如何使用命令行获取目录中的文件计数?)两次:第二次,sort -u
在使用sed
.这也适用于文件名中的换行符:
grep -c /
仅计算带有斜杠的行,因此仅考虑列表中多行文件名的第一行。输出
小脚本
我建议使用带有两个主要命令行的小型 bash shellscript(以及一个
filetype
便于切换以查找其他文件类型的变量)。它不查找或在符号链接中查找,只查找常规文件。
详细的 shellscript
这是一个更详细的版本,也考虑了符号链接,
测试输出
从简短的shellscript:
来自详细的 shellscript:
简单的 Perl 单行:
或者更简单的
find
命令:如果你喜欢打高尔夫球并且有最近的(比如不到十年的)Perl:
考虑使用比命令
locate
快得多的find
命令。在测试数据上运行
感谢 Muru 的回答,帮助我从Unix & Linux answer中的文件计数中删除符号链接。
感谢 Terdon在Unix & Linux answer
$PWD
中对(不是针对我)的回答。以下由评论引用的原始答案
简写:
sudo updatedb
如果今天创建了文件或者您今天删除了文件,则更新locate
命令使用的数据库。.c
.c
locate -cr "$PWD.*\.c$"
找到.c
当前目录中的所有文件及其子目录 ($PWD
)。而不是打印文件名,而是使用-c
参数打印计数。指定正则表达式而r
不是默认*pattern*
匹配,这会产生太多结果。locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l
. 找到*.c
当前目录及以下目录中的所有文件。删除文件名,sed
只保留目录名。使用 . 计算每个目录中的文件数uniq -c
。用 计算目录数wc -l
。单行从当前目录开始
注意文件数和目录数是如何变化的。我相信所有用户都拥有该
/usr/src
目录,并且可以根据已安装内核的数量以不同的计数运行上述命令。长表:
locate
长表格包括时间,因此您可以看到结束的速度有多快find
。即使您必须运行sudo updatedb
它也比单个find /
.注意:这是所有驱动器和分区上的所有文件。即我们也可以搜索 Windows 命令:
我有三个 Windows 10 NTFS 分区自动安装在
/etc/fstab
. 请注意,定位无所不知!有趣的计数:
统计 286,705 个目录中的 1,637,135 个文件需要 15 秒。YMMV。
有关
locate
命令正则表达式处理的详细分类(此问答中似乎不需要,但以防万一),请阅读以下内容:在某个特定目录下使用“定位”?最近文章的补充阅读: