特定の拡張子を持つファイルとそれらが入っているディレクトリをカウントするにはどうすればよいですか?


14

.c大規模で複雑なディレクトリ構造で拡張子を持つ通常のファイルの数、およびこれらのファイルが分散しているディレクトリの数を知りたい。必要な出力は、これらの2つの数値だけです。

ファイルの数を取得する方法についてこの質問を見てきましたが、ファイルが存在するディレクトリの数も知る必要があります。

  • ファイル名(ディレクトリを含む)に文字が含まれている可能性があります。.または-で始まり、スペースまたは改行がある場合があります。
  • 名前がで終わる.cシンボリックリンクと、ディレクトリへのシンボリックリンクがあるかもしれません。シンボリックリンクを追跡したりカウントしたりしたくない、または少なくともそれらがカウントされているかどうか、いつカウントされるのかを知りたい。
  • ディレクトリ構造には多くのレベルがあり、最上位ディレクトリ(作業ディレクトリ)には少なくとも1つの.cファイルがあります。

私は急いで(Bash)シェルでいくつかのコマンドを書いて自分でそれらを数えましたが、結果が正確だとは思いません...

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つのディレクトリに.cファイルが含まれ、29の通常のファイルが.cdotglobコマンドの実行時にオフの場合)で終わります(カウントを間違えた場合はお知らせください)。これらは私が欲しい数字です。

この特定のテストを使用しないでください。

NB:シェルまたは他の言語での回答はテストされ、評価されます。新しいパッケージをインストールする必要がある場合、問題ありません。GUIソリューションを知っている場合は、共有することをお勧めします(ただし、DE全体をインストールしてテストすることはできません):) Ubuntu MATE 17.10を使用しています。


悪いプログラミング習慣に対処するためのプログラムの作成は非常に困難であることが判明しました;)
WinEunuuchs2Unix

回答:


16

私はシンボリックリンクで出力を調べていませんが:

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ドットファイル、選択するには、このグロブを可能に.選択する通常のファイル(そうではなく、シンボリックリンク)、および:hプリント(のような唯一のディレクトリパスとファイル名ではないfindのを%h)(上のセクションを参照してください。ファイル名の生成修飾子を)。したがって、awkコマンドでは、表示される一意のディレクトリの数を数えるだけでよく、行数はファイル数です。


すごい。必要なものだけを使用し、それ以上は使用しません。教えてくれてありがとう:)
Zanna

@Zannaシンボリックリンクを使用してディレクトリ構造を再作成するコマンドと、シンボリックリンクを使用して予想される出力を作成する場合、それに応じてこれを修正できる可能性があります。
ムル

シンボリックリンクを使用して(通常は不必要に複雑な)テスト構造を作成するためのコマンドを追加しました。
ザンナ

@Zannaこのコマンドを取得するための調整は必要ないと思います29 7。に追加する-Lfind、それはになり41 10ます。どの出力が必要ですか?
ムル

1
zsh + awkメソッドを追加しました。zsh自体にカウントを表示させる方法はおそらくありますが、どうすればよいかわかりません。
ムル

11

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)

これは、名前がで終わるファイルを少なくとも1つ直接含むディレクトリの数を出力し.c、その後にスペースが続き、名前がで終わるファイルの数が続きます.c。「隠された」ファイル、つまり名前がで始まるファイルが.含まれ、隠されたディレクトリも同様に走査されます。

os.walkディレクトリ階層を再帰的に走査します。指定した開始点から再帰的にアクセス可能なすべてのディレクトリを列挙し、それぞれの情報を3つの値のタプルとして生成しますroot, dirs, files。移動する各ディレクトリ(名前を付けた最初のディレクトリを含む):

  • rootそのディレクトリのパス名を保持します。これは、システムの「ルートディレクトリ」とは全く無関係であることに注意してください/(とにも関係のない/rootことはないが)あなたがそこに開始した場合、それらにアクセスしてください。この場合、rootパス.、つまり現在のディレクトリから開始し、その下のすべての場所に移動します。
  • dirsは、名前が現在保持されているディレクトリのすべてのサブディレクトリのパス名のリストを保持しrootます。
  • files現在名前が保持されているが、それ自体がディレクトリではないディレクトリに存在するすべてのファイルのパス名のリストを保持しますroot。これには、シンボリックリンクを含む通常のファイル以外の種類のファイルが含まれますが、そのようなエントリが終わることを期待しておらず、そのようなエントリを.c見ることに興味があるようです。

この場合、タプルの3番目の要素filesfsスクリプトで呼び出す)のみを調べる必要があります。同様にfind、コマンド、Pythonのos.walk私のためのサブディレクトリへのトラバース。自分で調べなければならないのは、それぞれに含まれるファイルの名前だけです。findただし、コマンドとは異なり、os.walkこれらのファイル名のリストが自動的に提供されます。

そのスクリプトはシンボリックリンクをたどりません。サイクルを形成する可能性があるため、シンボリックリンクをたどることは非常に望ましくないでしょう。なぜなら、サイクルがない場合でも、異なるシンボリックリンクを介してアクセスできる場合、同じファイルとディレクトリが複数回走査され、カウントされる可能性があるためです。

os.walkシンボリックリンクをたどりたいと思った場合(通常はそうしません)、それを渡すことができますfollowlinks=true。つまり、書く代わりにを書くos.walk('.')ことができますos.walk('.', followlinks=true)。特に、ディレクトリ構造全体を再帰的に列挙し、要件に合うすべてのファイルをカウントするこのようなタスクの場合は、これが必要になることはほとんどありません。


7

検索+ 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:入力を1行ずつ読み取り(-n)、指定されたスクリプト-eを各行に適用します。-0入力行の区切りを設定し\0、我々はヌル区切りの入力を読み取ることができるようにします。
  • $k{$_}++$_は、現在の行の値をとる特別な変数です。これはhashの キーとして使用され%k、その値は各入力行(ディレクトリ名)が表示された回数です。
  • }{:これは簡単な書き方END{}です。}{すべての入力が処理された後、その後のコマンドは1回実行されます。
  • 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

私の提案は次のとおりです。

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

この短いスクリプトは、一時ファイルを作成し、現在のディレクトリ内およびその下にあるすべてのファイルを検索.cして、リストを一時ファイルに書き込みます。grep次に、ファイルをカウントするために使用されます(コマンドラインを使用してディレクトリ内のファイルのカウントを取得するには?)2回:2回目に、複数のリストにあるディレクトリは、を使用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

小さなシェルスクリプト

2つのメインコマンドライン(およびfiletype他のファイルタイプを探すために簡単に切り替えられるようにする変数)を備えた小さなbashシェルスクリプトをお勧めします。

通常のファイルのみを検索し、シンボリックリンクを検索しません。

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

詳細なシェルスクリプト

これは、シンボリックリンクも考慮したより詳細なバージョンです。

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

テスト出力

短いシェルスクリプトから:

$ ./ccntr 
29 7

詳細なシェルスクリプトから:

$ 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

単純なPerl oneライナー:

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, " $."'

ゴルフが好きで、最近の(10年未満の)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," $."'

2

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の回答ます

Unix&Linux answer$PWD(私に向けられていない)答えてくれたTerdonに感謝します


コメントで参照される以下の元の回答

ショートフォーム:

$ 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指定は、デフォルトの代わりにregexで*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、1つよりも何倍も高速です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
───────────────────────────────────────────────────────────────────────────────────────────

注:これは、上のすべてのファイルであるALLのドライブとパーティション。つまり、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

3つのWindows 10 NTFSパーティションが自動的にマウントされています /etc/fstabます。Locateはすべてを知っていることに注意してください!

興味深いカウント:

$ 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コマンドの正規表現処理の詳細については(このQ&Aでは必要ないように見えますが、念のために使用してください)、これを読んでください

最近の記事からの追加読書:


1
これは、特定のディレクトリ内のファイルをカウントしません。あなたが指摘するように、それは一致するすべてのファイル(またはディレクトリ、または他のタイプのファイル)をカウントします(あなたが引用していないので現在のディレクトリに.c名前の付いたファイルがある場合は壊れることに注意してください)システムでは、.cファイルが含まれているかどうかに関係なく。-.c*.c
テルドン

@terdonディレクトリを渡すことができ~/my_c_progs/*.cます。.cプログラムを含む638個のディレクトリがカウントされ、合計ディレクトリはとして表示され286,705ます。二重引用符 `" * .c "の答えを修正します。ヒントをありがとう。
WinEunuuchs2Unix

3
はい、のようなものを使用できますがlocate -r "/path/to/dir/.*\.c$"、回答のどこにも記載されていません。これに言及している別の回答へのリンクのみを提供しますが、ここで尋ねられている質問に答えるためにそれを適応させる方法の説明はありません。あなたの全体の答えは、システム上のファイルとディレクトリの総数を数える方法に焦点を当てています。これは、「。cファイルの数と。特定のディレクトリ内のcファイル」。また、数字が間違っています。OPの例で試してください。
テルドン

@terdonご意見ありがとうございます。私はあなたの提案とあなたが$PWD変数のために他のSEサイトに投稿した答えで答えを改善しました:unix.stackexchange.com/a/188191/200094
WinEunuuchs2Unix

1
次に$PWD、正規表現で特殊な文字が含まれていないことを確認する必要があります
-muru
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.