大規模で複雑なディレクトリ構造で拡張子.c
を持つ通常のファイルの数と、これらのファイルが分散しているディレクトリの数を知りたい。必要な出力は、これら2つの数値だけです。
この質問を見ました ファイルの数を取得する方法についてですが、ファイルが入っているディレクトリの数も知る必要があります。
.
または-
で始まり、スペースまたは改行が含まれる場合があります。.c
で終わるシンボリックリンクと、ディレクトリへのシンボリックリンクがあるかもしれません。シンボリックリンクをフォローしたりカウントしたりしたくない、または少なくともカウントされているかどうか、いつカウントしたいのか知りたい。.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。を使用します.
私はシンボリックリンクで出力を調べていませんが:
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
で改行で区切られた出力に変換できるようにします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コマンドでは、表示される一意のディレクトリの数を数えるだけでよく、行数はファイル数です。
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)
と書くことができます。特に、ディレクトリ構造全体を再帰的に列挙し、要件に合うすべてのファイルをカウントするこのようなタスクの場合は、ほとんど必要ないことを繰り返します。
検索+ 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" '
私の提案は次のとおりです。
#!/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
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
$
単純な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
ディレクトリを持ち、インストールされているカーネルの数に応じて異なるカウントで上記のコマンドを実行できると思います。
長い形式には時間が含まれているため、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
───────────────────────────────────────────────────────────────────────────────────────────
注:これは、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」を使用しますか?
最近の記事からの追加読書: