如何计算具有特定扩展名的文件数量以及它们所在的目录?

如何计算具有特定扩展名的文件数量以及它们所在的目录?

我想知道在一个大型复杂目录结构中有多少常规文件具有扩展名.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结尾(如果我数错了,请告诉我)。这些就是我想要的数字。.cdotglob

请随时不是使用这个特殊的测试。

注意:我会测试并感谢使用任何 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返回哈希表中键的数组%kscalar 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
$ 

相关内容