我想知道在一个大型复杂目录结构中有多少常规文件具有扩展名.c
,以及这些文件分布在多少个目录中。我想要的输出只是这两个数字。
我见过这个问题关于如何获取文件数量,但我需要知道文件所在的目录数量。
- 我的文件名(包括目录)可能包含任何字符;它们可能以
.
或开头,-
并且有空格或换行符。 - 我可能有一些名称以 结尾的符号链接
.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
输出在文件名中打印换行符) 并写入一大堆空文件(哎呀)。
我如何才能可靠地枚举我的.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 个目录包含文件,29 个常规文件以(if is off when the command is run).c
结尾(如果我数错了,请告诉我)。这些就是我想要的数字。.c
dotglob
请随时不是使用这个特殊的测试。
注意:我会测试并感谢使用任何 shell 或其他语言的答案。如果我必须安装新软件包,那也没问题。如果您知道 GUI 解决方案,我鼓励您分享(但我可能不会安装整个 DE 来测试它):)我使用 Ubuntu MATE 17.10。
答案1
我还没有检查符号链接的输出但是:
find . -type f -iname '*.c' -printf '%h\0' |
sort -z |
uniq -zc |
sed -zr 's/([0-9]) .*/\1 1/' |
tr '\0' '\n' |
awk '{f += $1; d += $2} END {print f, d}'
- 该
find
命令打印它找到的每个文件的目录名.c
。 sort | uniq -c
将告诉我们每个目录中有多少个文件(sort
这里可能不需要,不确定)sed
,我将目录名称替换为,1
从而消除了所有可能的奇怪字符,只保留计数和1
剩余的- 使我能够转换为换行符分隔的输出
tr
- 然后我用 awk 将其相加,得到文件总数和包含这些文件的目录数。请注意,
d
这里与 基本相同NR
。我可以省略插入1
命令sed
,而只是NR
在这里打印,但我认为这更清楚一些。
直到tr
,数据都是以 NUL 分隔的,对于所有有效的文件名都是安全的。
使用 zsh 和 bash,您可以使用printf %q
获取带引号的字符串,其中不包含换行符。因此,您可以执行以下操作:
shopt -s globstar dotglob nocaseglob
printf "%q\n" **/*.c | awk -F/ '{NF--; f++} !c[$0]++{d++} END {print f, d}'
然而,尽管**
不应扩展目录的符号链接,我无法在 bash 4.4.18(1)(Ubuntu 16.04)上获得所需的输出。
$ shopt -s globstar dotglob nocaseglob
$ printf "%q\n" ./**/*.c | awk -F/ '{NF--; f++} !c[$0]++{d++} END {print f, d}'
34 15
$ echo $BASH_VERSION
4.4.18(1)-release
但是 zsh 工作正常,并且命令可以简化:
$ printf "%q\n" ./**/*.c(D.:h) | awk '!c[$0]++ {d++} END {print NR, d}'
29 7
D
启用此 glob 来选择点文件,.
选择常规文件(因此,不是符号链接),并且:h
仅打印目录路径而不是文件名(例如find
)%h
(请参阅文件名生成和修饰符)。所以用 awk 命令我们只需要统计出现的唯一目录的数量,行数就是文件数。
答案2
Python 有os.walk
,这使得此类任务变得简单、直观,并且自动变得强大,即使面对奇怪的文件名(例如包含换行符的文件名)。这个 Python 3 脚本是我最初发布的在聊天中,旨在在当前目录中运行(但它不必位于当前目录中,您可以更改它传递的路径os.walk
):
#!/usr/bin/env python3
import os
dc = fc = 0
for _, _, fs in os.walk('.'):
c = sum(f.endswith('.c') for f in fs)
if c:
dc += 1
fc += c
print(dc, fc)
这将打印直接包含至少一个名称以 结尾.c
、后跟空格的文件的目录数,然后是名称以 结尾的文件数.c
。“隐藏”文件(即名称以 -- 开头的文件)也.
包括在内,隐藏目录也同样被遍历。
os.walk
递归遍历目录层次结构。它枚举从您指定的起点递归访问的所有目录,并以三个值的元组的形式生成有关每个目录的信息。root, dirs, files
对于它遍历到的每个目录(包括您指定名称的第一个目录):
root
保存该目录的路径名。请注意,这与系统的“根目录”完全无关/
(也与 无关/root
),尽管它会如果你从那里开始,就转到那些。在这种情况下,root
从路径.
(即当前目录)开始,并转到其下方的所有位置。dirs
保存所有子目录当前保存其名称的目录root
。files
保存所有文件位于当前保存名称的目录中,root
但本身不是目录。请注意,这包括除常规文件之外的其他类型的文件,包括符号链接,但听起来您不希望任何此类条目以 结尾,.c
并且有兴趣查看任何以 结尾的条目。
在这种情况下,我只需要检查元组的第三个元素files
(我fs
在脚本中调用它)。与find
命令一样,Python 会os.walk
为我遍历子目录;我唯一需要自己检查的是每个子目录包含的文件的名称。find
但与命令不同的是,它os.walk
会自动为我提供这些文件名的列表。
该脚本不遵循符号链接。你很可能不希望在这样的操作中遵循符号链接,因为它们可能形成循环,并且即使没有循环,如果可以通过不同的符号链接访问相同的文件和目录,则可能会被遍历和计数多次。
如果您确实想要os.walk
跟踪符号链接(通常您不会这样做),那么您可以传递followlinks=true
给它。也就是说,os.walk('.')
您可以写os.walk('.', followlinks=true)
而不是 。我重申,您很少会想要这样做,尤其是对于像这样的任务,您需要递归枚举整个目录结构(无论它有多大),并计算其中满足某些要求的所有文件。
答案3
查找 + Perl:
$ find . -type f -iname '*.c' -printf '%h\0' |
perl -0 -ne '$k{$_}++; }{ print scalar keys %k, " $.\n" '
7 29
解释
该find
命令将找到任何常规文件(因此没有符号链接或目录),然后打印它们所在目录的名称(%h
),后跟\0
。
perl -0 -ne
:逐行读取输入(-n
)并将 给出的脚本应用于-e
每一行。-0
将输入行分隔符设置为,\0
以便我们可以读取以空分隔的输入。$k{$_}++
:$_
是一个特殊变量,它取当前行的值。这用作哈希%k
,其值是每个输入行(目录名)被看到的次数。}{
:这是 的简写方式。处理完所有输入后, 之后的END{}
所有命令都将执行一次。}{
print scalar keys %k, " $.\n"
:keys %k
返回哈希表中键的数组%k
。scalar keys %k
给出该数组中的元素数,即看到的目录数。这与 的当前值一起打印$.
, 是一个特殊变量,用于保存当前输入的行号。由于这是在最后运行的,因此当前输入的行号将是最后一行的行号,因此是迄今为止看到的行数。
为了清楚起见,您可以将 perl 命令扩展如下:
find . -type f -iname '*.c' -printf '%h\0' |
perl -0 -e 'while($line = <STDIN>){
$dirs{$line}++;
$tot++;
}
$count = scalar keys %dirs;
print "$count $tot\n" '
答案4
小型 shellscript
filetype
我建议使用一个带有两个主命令行(以及一个变量,以便于切换以查找其他文件类型)的小型 bash shellscript 。
它不查找符号链接,只查找常规文件。
#!/bin/bash
filetype=c
#filetype=pdf
# count the 'filetype' files
find -type f -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l | tr '\n' ' '
# count directories containing 'filetype' files
find -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)'" \;|grep 'contains file(s)$'|wc -l
详细的 shell 脚本
这是一个更详细的版本,还考虑了符号链接,
#!/bin/bash
filetype=c
#filetype=pdf
# counting the 'filetype' files
echo -n "number of $filetype files in the current directory tree: "
find -type f -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l
echo -n "number of $filetype symbolic links in the current directory tree: "
find -type l -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l
echo -n "number of $filetype normal files in the current directory tree: "
find -type f -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l
echo -n "number of $filetype symbolic links in the current directory tree including linked directories: "
find -L -type f -name "*.$filetype" -ls 2> /tmp/c-counter |sed 's#.* \./##' | wc -l; cat /tmp/c-counter; rm /tmp/c-counter
# list directories with and without 'filetype' files (good for manual checking; comment away after test)
echo '---------- list directories:'
find -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)' || echo '{} empty'" \;
echo ''
#find -L -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)' || echo '{} empty'" \;
# count directories containing 'filetype' files
echo -n "number of directories with $filetype files: "
find -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)'" \;|grep 'contains file(s)$'|wc -l
# list and count directories including symbolic links, containing 'filetype' files
echo '---------- list all directories including symbolic links:'
find -L -type d -exec bash -c "ls -AF '{}' |grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)' || echo '{} empty'" \;
echo ''
echo -n "number of directories (including symbolic links) with $filetype files: "
find -L -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)'" \; 2>/dev/null |grep 'contains file(s)$'|wc -l
# count directories without 'filetype' files (good for checking; comment away after test)
echo -n "number of directories without $filetype files: "
find -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null || echo '{} empty'" \;|grep 'empty$'|wc -l
测试输出
来自简短的 shellscript:
$ ./ccntr
29 7
来自详细的 shellscript:
$ LANG=C ./c-counter
number of c files in the current directory tree: 29
number of c symbolic links in the current directory tree: 1
number of c normal files in the current directory tree: 29
number of c symbolic links in the current directory tree including linked directories: 42
find: './cfiles/2/2': Too many levels of symbolic links
find: './cfiles/dirlink/2': Too many levels of symbolic links
---------- list directories:
. empty
./cfiles contains file(s)
./cfiles/2 contains file(s)
./cfiles/2/b contains file(s)
./cfiles/2/a contains file(s)
./cfiles/3 empty
./cfiles/3/b contains file(s)
./cfiles/3/a empty
./cfiles/1 contains file(s)
./cfiles/1/b empty
./cfiles/1/a empty
./cfiles/space d contains file(s)
number of directories with c files: 7
---------- list all directories including symbolic links:
. empty
./cfiles contains file(s)
./cfiles/2 contains file(s)
find: './cfiles/2/2': Too many levels of symbolic links
./cfiles/2/b contains file(s)
./cfiles/2/a contains file(s)
./cfiles/3 empty
./cfiles/3/b contains file(s)
./cfiles/3/a empty
./cfiles/dirlink empty
find: './cfiles/dirlink/2': Too many levels of symbolic links
./cfiles/dirlink/b contains file(s)
./cfiles/dirlink/a contains file(s)
./cfiles/1 contains file(s)
./cfiles/1/b empty
./cfiles/1/a empty
./cfiles/space d contains file(s)
number of directories (including symbolic links) with c files: 9
number of directories without c files: 5
$