Zan*_*nna 15 command-line bash scripts files find
我想知道.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
Run Code Online (Sandbox Code Playgroud)
这会输出关于不明确重定向的抱怨,错过当前目录中的文件,并在特殊字符上绊倒(例如,重定向find输出在文件名中打印换行符)并写入一大堆空文件(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
Run Code Online (Sandbox Code Playgroud)
在生成的结构中,7 个目录包含.c文件,29 个常规文件以.c(如果dotglob在运行命令时关闭)(如果我数错了,请告诉我)。这些是我想要的数字。
请随意不要使用此特定测试。
注意:我将测试和欣赏任何 shell 或其他语言的答案。如果我必须安装新软件包,没问题。如果您知道 GUI 解决方案,我鼓励您分享(但我可能不会安装整个 DE 来测试它):) 我使用 Ubuntu MATE 17.10。
mur*_*uru 17
我没有用符号链接检查输出,但是:
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}'
Run Code Online (Sandbox Code Playgroud)
find命令打印.c它找到的每个文件的目录名称。sort | uniq -c将为我们提供每个目录中有多少文件(sort这里可能没有必要,不确定)sed,我用 替换目录名1,从而消除所有可能的奇怪字符,只有计数和1剩余trd这里与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}'
Run Code Online (Sandbox Code Playgroud)
但是,即使**不应该将符号链接扩展到目录,我也无法在 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
Run Code Online (Sandbox Code Playgroud)
但是 zsh 工作正常,命令可以简化:
$ printf "%q\n" ./**/*.c(D.:h) | awk '!c[$0]++ {d++} END {print NR, d}'
29 7
Run Code Online (Sandbox Code Playgroud)
D启用此 glob 以选择点文件,.选择常规文件(因此,不是符号链接),并:h仅打印目录路径而不是文件名(如find's %h)(请参阅文件名生成和修饰符部分)。所以使用 awk 命令我们只需要计算出现的唯一目录的数量,行数就是文件数。
Eli*_*gan 12
Python 具有os.walk,即使面对奇怪的文件名(例如包含换行符的文件名),这样的任务也变得简单、直观且自动健壮。这个 Python 3 脚本,我最初发布在 chat 中,打算在当前目录中运行(但它不必位于当前目录中,您可以更改它传递到的路径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)
Run Code Online (Sandbox Code Playgroud)
这将打印直接包含至少一个.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). 我重申,您很少需要这样做,尤其是对于这样的任务,您递归地枚举整个目录结构,无论它有多大,并计算其中满足某些要求的所有文件。
查找 + Perl:
$ find . -type f -iname '*.c' -printf '%h\0' |
perl -0 -ne '$k{$_}++; }{ print scalar keys %k, " $.\n" '
7 29
Run Code Online (Sandbox Code Playgroud)
该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 命令扩展为:
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" '
Run Code Online (Sandbox Code Playgroud)