AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / ubuntu / 问题 / 1023244
Accepted
Zanna
Zanna
Asked: 2018-04-09 23:29:54 +0800 CST2018-04-09 23:29:54 +0800 CST 2018-04-09 23:29:54 +0800 CST

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

  • 772

我想知道在大型复杂目录结构中有多少常规文件具有扩展名.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。

scripts command-line bash files find
  • 7 7 个回答
  • 8449 Views

7 个回答

  • Voted
  1. Best Answer
    muru
    2018-04-10T00:36:50+08:002018-04-10T00:36:50+08:00

    我没有使用符号链接检查输出,但是:

    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's %h)(参见文件名生成和修饰符部分)。所以使用 awk 命令我们只需要计算出现的唯一目录的数量,行数就是文件数。

    • 17
  2. Eliah Kagan
    2018-04-10T01:27:33+08:002018-04-10T01:27:33+08:00

    Python 具有os.walk,即使在面对奇怪的文件名(例如包含换行符的文件名)时,也可以使此类任务变得简单、直观且自动稳健。我最初在 chat中发布的这个 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保存该目录的路径名。请注意,这与系统的“根目录”完全无关(/也与. 在这种情况下,从路径开始——即,当前目录——然后到它下面的任何地方。/rootroot.
    • dirs包含目录的所有子目录的路径名列表,其名称当前保存在root.
    • files包含所有文件的路径名列表,这些文件位于当前保存名称root但本身不是目录的目录中。请注意,这包括除常规文件之外的其他类型的文件,包括符号链接,但听起来您不希望任何此类条目以结尾.c并且有兴趣看到任何这样的条目。

    在这种情况下,我只需要检查元组的第三个元素files(我fs在脚本中调用它)。像find命令一样,Pythonos.walk为我遍历子目录;我唯一需要检查自己的是每个文件包含的文件的名称。find但是,与命令不同的是,它会os.walk自动为我提供这些文件名的列表。

    该脚本不遵循符号链接。您很可能不希望此类操作遵循符号链接,因为它们可能形成循环,并且因为即使没有循环,如果可以通过不同的符号链接访问相同的文件和目录,它们也可能会被多次遍历和计数。

    如果您确实想要os.walk遵循符号链接(通常不会这样做),那么您可以传递followlinks=true给它。也就是说,os.walk('.')你可以写而不是写os.walk('.', followlinks=true)。我重申,您很少需要这样做,特别是对于像这样的任务,您递归地枚举整个目录结构,无论它有多大,并计算其中满足某些要求的所有文件。

    • 12
  3. terdon
    2018-04-11T01:16:26+08:002018-04-11T01:16:26+08:00

    查找 + 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{$_}++:$_是一个特殊的变量,它取当前行的值。这用作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" '
    
    • 7
  4. dessert
    2018-04-09T23:57:14+08:002018-04-09T23:57:14+08:00

    这是我的建议:

    #!/bin/bash
    tempfile=$(mktemp)
    find -type f -name "*.c" -prune >$tempfile
    grep -c / $tempfile
    sed 's_[^/]*$__' $tempfile | sort -u | grep -c /
    

    这个简短的脚本创建一个临时文件,查找当前目录中和下的每个文件,并将.c列表写入临时文件。grep然后用于对文件进行计数(按照如何使用命令行获取目录中的文件计数?)两次:第二次,sort -u在使用sed.

    这也适用于文件名中的换行符:grep -c /仅计算带有斜杠的行,因此仅考虑列表中多行文件名的第一行。

    输出

    $ tree
    .
    ├── 1
    │   ├── 1
    │   │   ├── test2.c
    │   │   └── test.c
    │   └── 2
    │       └── test.c
    └── 2
        ├── 1
        │   └── test.c
        └── 2
    
    $ tempfile=$(mktemp);find -type f -name "*.c" -prune >$tempfile;grep -c / $tempfile;sed 's_[^/]*$__' $tempfile | sort -u | grep -c /
    4
    3
    
    • 4
  5. sudodus
    2018-04-10T07:35:21+08:002018-04-10T07:35:21+08:00

    小脚本

    我建议使用带有两个主要命令行的小型 bash shellscript(以及一个filetype便于切换以查找其他文件类型的变量)。

    它不查找或在符号链接中查找,只查找常规文件。

    #!/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
    

    详细的 shellscript

    这是一个更详细的版本,也考虑了符号链接,

    #!/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
    $ 
    
    • 4
  6. Hynek -Pichi- Vychodil
    2018-04-11T04:15:40+08:002018-04-11T04:15:40+08:00

    简单的 Perl 单行:

    perl -MFile::Find=find -le'find(sub{/\.c\z/ and -f and $c{$File::Find::dir}=++$c}, @ARGV); print 0 + keys %c, " $c"' dir1 dir2
    

    或者更简单的find命令:

    find dir1 dir2 -type f -name '*.c' -printf '%h\0' | perl -l -0ne'$c{$_}=1}{print 0 + keys %c, " $."'
    

    如果你喜欢打高尔夫球并且有最近的(比如不到十年的)Perl:

    perl -MFile::Find=find -E'find(sub{/\.c$/&&-f&&($c{$File::Find::dir}=++$c)},".");say 0+keys%c," $c"'
    
    find -type f -name '*.c' -printf '%h\0'|perl -0nE'$c{$_}=1}{say 0+keys%c," $."'
    
    • 4
  7. WinEunuuchs2Unix
    2018-04-10T15:37:17+08:002018-04-10T15:37:17+08:00

    考虑使用比命令locate快得多的find命令。

    在测试数据上运行

    $ sudo updatedb # necessary if files in focus were added `cron` daily.
    $ printf "Number Files: " && locate -0r "$PWD.*\.c$" | xargs -0 -I{} sh -c 'test ! -L "$1" && echo "regular file"' _  {} | wc -l &&  printf "Number Dirs.: " && locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -cu | wc -l
    Number Files: 29
    Number Dirs.: 7
    

    感谢 Muru 的回答,帮助我从Unix & Linux answer中的文件计数中删除符号链接。

    感谢 Terdon在Unix & Linux answer$PWD中对(不是针对我)的回答。


    以下由评论引用的原始答案

    简写:

    $ cd /
    $ sudo updatedb
    $ printf "Number Files: " && locate -cr "$PWD.*\.c$"
    Number Files: 3523
    $ printf "Number Dirs.: " && locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l 
    Number Dirs.: 648
    
    • 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。

    单行从当前目录开始

    $ cd /usr/src
    $ printf "Number Files: " && locate -cr "$PWD.*\.c$" &&  printf "Number Dirs.: " && locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l
    Number Files: 3430
    Number Dirs.: 624
    

    注意文件数和目录数是如何变化的。我相信所有用户都拥有该/usr/src目录,并且可以根据已安装内核的数量以不同的计数运行上述命令。

    长表:

    locate长表格包括时间,因此您可以看到结束的速度有多快find。即使您必须运行sudo updatedb它也比单个find /.

    ───────────────────────────────────────────────────────────────────────────────────────────
    rick@alien:~/Downloads$ sudo time updatedb
    0.58user 1.32system 0:03.94elapsed 48%CPU (0avgtext+0avgdata 7568maxresident)k
    48inputs+131920outputs (1major+3562minor)pagefaults 0swaps
    ───────────────────────────────────────────────────────────────────────────────────────────
    rick@alien:~/Downloads$ time (printf "Number Files: " && locate -cr $PWD".*\.c$")
    Number Files: 3523
    
    real    0m0.775s
    user    0m0.766s
    sys     0m0.012s
    ───────────────────────────────────────────────────────────────────────────────────────────
    rick@alien:~/Downloads$ time (printf "Number Dirs.: " && locate -r $PWD".*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l) 
    Number Dirs.: 648
    
    real    0m0.778s
    user    0m0.788s
    sys     0m0.027s
    ───────────────────────────────────────────────────────────────────────────────────────────
    

    注意:这是所有驱动器和分区上的所有文件。即我们也可以搜索 Windows 命令:

    $ time (printf "Number Files: " && locate *.exe -c)
    Number Files: 6541
    
    real    0m0.946s
    user    0m0.761s
    sys     0m0.060s
    ───────────────────────────────────────────────────────────────────────────────────────────
    rick@alien:~/Downloads$ time (printf "Number Dirs.: " && locate *.exe | sed 's%/[^/]*$%/%' | uniq -c | wc -l) 
    Number Dirs.: 3394
    
    real    0m0.942s
    user    0m0.803s
    sys     0m0.092s
    

    我有三个 Windows 10 NTFS 分区自动安装在/etc/fstab. 请注意,定位无所不知!

    有趣的计数:

    $ time (printf "Number Files: " && locate / -c &&  printf "Number Dirs.: " && locate / | sed 's%/[^/]*$%/%' | uniq -c | wc -l)
    Number Files: 1637135
    Number Dirs.: 286705
    
    real    0m15.460s
    user    0m13.471s
    sys     0m2.786s
    

    统计 286,705 个目录中的 1,637,135 个文件需要 15 秒。YMMV。

    有关locate命令正则表达式处理的详细分类(此问答中似乎不需要,但以防万一),请阅读以下内容:在某个特定目录下使用“定位”?

    最近文章的补充阅读:

    • Tecmint - 10 个有用的 'locate' 命令实用示例,适用于 Linux 新手
    • HowtoForge - 适合初学者的 Linux 定位命令(8 个示例)
    • Computer Hope - Linux 定位命令
    • 2

相关问题

  • 如何从命令行仅安装安全更新?关于如何管理更新的一些提示

  • 如何从命令行刻录双层 dvd iso

  • 如何从命令行判断机器是否需要重新启动?

  • 文件权限如何工作?文件权限用户和组

  • 如何在 Vim 中启用全彩支持?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    如何运行 .sh 脚本?

    • 16 个回答
  • Marko Smith

    如何安装 .tar.gz(或 .tar.bz2)文件?

    • 14 个回答
  • Marko Smith

    如何列出所有已安装的软件包

    • 24 个回答
  • Marko Smith

    无法锁定管理目录 (/var/lib/dpkg/) 是另一个进程在使用它吗?

    • 25 个回答
  • Martin Hope
    Flimm 如何在没有 sudo 的情况下使用 docker? 2014-06-07 00:17:43 +0800 CST
  • Martin Hope
    Ivan 如何列出所有已安装的软件包 2010-12-17 18:08:49 +0800 CST
  • Martin Hope
    La Ode Adam Saputra 无法锁定管理目录 (/var/lib/dpkg/) 是另一个进程在使用它吗? 2010-11-30 18:12:48 +0800 CST
  • Martin Hope
    David Barry 如何从命令行确定目录(文件夹)的总大小? 2010-08-06 10:20:23 +0800 CST
  • Martin Hope
    jfoucher “以下软件包已被保留:”为什么以及如何解决? 2010-08-01 13:59:22 +0800 CST
  • Martin Hope
    David Ashford 如何删除 PPA? 2010-07-30 01:09:42 +0800 CST

热门标签

10.10 10.04 gnome networking server command-line package-management software-recommendation sound xorg

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve