web-dev-qa-db-ja.com

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

大規模で複雑なディレクトリ構造で拡張子.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

これは、あいまいなリダイレクトに関する苦情を出力し、現在のディレクトリ内のファイルを見逃し、特殊文字につまずく(たとえば、 redirected find output prints newlines in 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がオフの場合)(誤算した場合は、私が知っている)。これらは私が欲しい数字です。

この特定のテストを使用することをお気軽にnotしてください。

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

14
Zanna

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

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と同じであることに注意してください。 sedコマンドに1を挿入することを省略し、ここに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コマンドでは、表示される一意のディレクトリの数を数えるだけでよく、行数はファイル数です。

16
muru

Pythonには os.walk があり、改行文字を含むような奇妙なファイル名に直面しても、このようなタスクを簡単で直感的で自動的に堅牢にします。もともと投稿していたこのPython 3スクリプト チャット は、現在のディレクトリで実行することを意図しています(ただしitは現在のディレクトリにある必要はありません。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で終わるファイルが少なくとも1つ直接含まれるディレクトリの数を出力し、その後にスペースが続き、その後に名前が.cで終わるファイルの数が続きます。 「隠された」ファイル、つまり名前が.で始まるファイルが含まれ、隠されたディレクトリも同様に走査されます。

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

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

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

そのスクリプトはシンボリックリンクをたどりません。あなたはおそらくdo n'tシンボリックリンクがこのような操作に従うことを望みますサイクルを形成します。サイクルがない場合でも、同じシンボリックリンクを介してアクセスできる場合、同じファイルとディレクトリが複数回走査され、カウントされる場合があります。

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

11
Eliah Kagan

検索+ 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{}を書く簡単な方法です。 }{の後のコマンドは、すべての入力が処理された後、1回実行されます。
  • 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
terdon

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

#!/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回目、複数回リストされているディレクトリsedを使用して各行からファイル名を削除した後、sort -uを使用して削除されます。

これは、ファイル名の改行でも適切に機能します。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
dessert

小さなシェルスクリプト

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
sudodus

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

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

Unix&Linux answer のファイルカウントからシンボリックリンクを削除するのを助けてくれたMuruの回答に感謝します。

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.cファイルが今日作成された場合、または.cファイルを今日削除した場合、locateコマンドで使用されるデータベースを更新します。
  • 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ディレクトリを持ち、インストールされているカーネルの数に応じて異なるカウントで上記のコマンドを実行できると思います。

長い形式:

長い形式には時間が含まれているため、locatefindを超える速度を確認できます。 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
───────────────────────────────────────────────────────────────────────────────────────────

注:これは、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

/etc/fstabに自動的にマウントされた3つのWindows 10 NTFSパーティションがあります。 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では必要ないように見えますが、念のために使用します)、以下をお読みください: 特定のディレクトリの下で「locate」を使用しますか?

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

2