ファイルをカウントするbashコマンドはありますか?


182

パターンに一致するファイルの数をカウントするbashコマンドはありますか?

たとえば、次のパターンに一致するディレクトリ内のすべてのファイルの数を取得したいとします。 log*

回答:


243

この単純なワンライナーは、bashだけでなく、どのシェルでも機能するはずです。

ls -1q log* | wc -l

ls -1qは、空白や改行などの特殊文字が含まれている場合でも、ファイルごとに1行を提供します。

出力はwc -lにパイプされ、行数がカウントされます。


10
私はを使用しません。-lこれはstat(2)、各ファイルに必要であり、カウントの目的では何も追加されないためです。
camh 2007

12
ls子プロセスを作成するため、私はを使用しません。 log*シェルではなくによって展開されるlsので、シンプルechoに実行できます。
cdarke

2
スペースや特殊文字を含むファイル名がある場合、エコーは機能しません。
ダニエル

4
@WalterTrossそのとおりです(その効率は元の質問の要件ではありませんでした)。また、出力が端末でない場合でも、-qが改行付きのファイルを処理することもわかりました。そして、これらのフラグは、私がテストしたすべてのプラットフォームとシェルでサポートされています。あなたと入力のcamhのおかげで、答えを更新します!
ダニエル

3
logs問題のディレクトリに呼び出されたディレクトリがある場合、そのログディレクトリの内容もカウントされます。これはおそらく意図的なものではありません。
mogsie

54

これは\n、bashを使用して安全に(つまり、スペースやファイル名に含まれるファイルによってバグが発生することはありません)実行できます。

$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}

一致するファイルがない場合に配列内のnullglobリテラル*.logを取得しないように有効にする必要があります。(安全にリセットする方法の例については、「set -x」を「元に戻す」方法を参照してください。)$logfiles


2
おそらく、明示的にこれがBash-であることを指摘だけで特にに完全にスピードアップするためにまだない新しい訪問者のために、答えのshとbashの違い
tripleee

また、ファイナルshopt -u nullglobnullglob設定されていない場合は、スキップして、開始します。
tripleee

注:で置換する*.logと、*ディレクトリがカウントされます。列挙するファイルに従来の命名規則がname.extensionある場合は、を使用します*.*
AlainD

52

ここにはたくさんの答えがありますが、いくつかは考慮に入れていません

  • スペース、改行、または制御文字が含まれるファイル名
  • ハイフンで始まるファイル名(というファイルを想像してください-l
  • ドットで始まる隠しファイル(グロブの*.log代わりにlog*
  • グロブlogsと一致するディレクトリ(たとえば、と呼ばれるディレクトリlog*
  • 空のディレクトリ(つまり、結果は0)
  • 非常に大きなディレクトリ(それらすべてをリストすると、メモリを使い果たす可能性があります)

これらすべてを処理するソリューションは次のとおりです。

ls 2>/dev/null -Ubad1 -- log* | wc -l

説明:

  • -U原因lsそれを意味しないソートのエントリには、メモリ内にリストディレクトリ全体をロードする必要はありません。
  • -b非グラフィック文字のCスタイルのエスケープを印刷し、改行がとして印刷されることを決定的にし\nます。
  • -a隠しファイルも含めてすべてのファイルを出力します(グロブlog*が隠しファイルを含まない場合は厳密には必要ありません)
  • -dディレクトリーの内容をリストすることを試みずにディレクトリーを印刷しlsます。これは通常行うことです
  • -1 それが1つの列にあることを確認します(パイプに書き込むときにlsがこれを自動的に行うので、厳密には必要ありません)
  • 2>/dev/nullstderrをリダイレクトするので、ログファイルが0の場合、エラーメッセージは無視されます。(代わりに、作業ディレクトリ全体がリストさshopt -s nullglobれることlsに注意してください。)
  • wc -l生成されているディレクトリリストを消費するため、の出力はlsいつでもメモリに保存されません。
  • ----への引数として理解されないように、ファイル名はコマンドから分離されていますlslog*削除された場合)

シェルlog*ファイルの完全なリストに展開します。ファイルの数が多い場合、メモリが使い果たされる可能性があるため、grepを介して実行することをお勧めします。

ls -Uba1 | grep ^log | wc -l

最後の1つは、大量のメモリを使用せずに非常に大きなファイルのディレクトリを処理します(サブシェルを使用します)。-dそれは、現在のディレクトリの内容をリストているため、もはや必要ではありません。


48

再帰検索の場合:

find . -type f -name '*.log' -printf x | wc -c

wc -c出力の文字数をカウントするfind一方で、-printf x伝えfindシングルを印刷しますx、各結果のために。

非再帰的な検索の場合は、次のようにします。

find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c

6
場合でも、あなたがスペースを含むファイルを持っていない、あなたのスクリプトのいくつかの他のユーザーは、スクリプトが失敗する原因、悪意を持ってという名前のファイルが発生する場合があります。また、StackOverflowでこれに遭遇する他の人々は、改行を含むファイルを持っている可能性があり、落とし穴を知る必要があります。
mogsie

参考までに、単に除外-name '*.log'すると、すべてのファイルがカウントされます。これは、私のユースケースで必要なものです。また、-maxdepthフラグは非常に便利です。
starmandeluxe 2018

2
改行が含まれているファイル名がある場合、これでも正しくない結果が生成されます。回避策はで簡単findです。逐語的ファイル名以外のものを印刷するだけです。
tripleee

8

この質問に対する受け入れられた回答は間違っていますが、私は担当者が少ないため、コメントを追加できません。

この質問に対する正しい答えは、Matによって与えられます。

shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}

受け入れられた回答の問題は、wc -lが改行文字の数をカウントし、端末に「?」として出力された場合でもそれらをカウントすることです。'ls -l'の出力。これは、ファイル名に改行文字が含まれていると、受け入れられた回答が失敗することを意味します。提案されたコマンドをテストしました:

ls -l log* | wc -l

名前に改行文字が含まれているパターンに一致するファイルが1つしかない場合でも、誤って値2が報告されます。例えば:

touch log$'\n'def
ls log* -l | wc -l

6

多くのファイルがあり、エレガントshopt -s nullglobでbashの配列ソリューションを使用したくない場合は、ファイル名(改行が含まれている可能性がある)を印刷しない限り、findなどを使用できます。

find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l

これは、log *に一致し、かつで始まらないすべてのファイルを検索します.*—「not name。*」は冗長ですが、「ls」のデフォルトはドットファイルを表示せず、デフォルトを表示することに注意することが重要です検索のためにそれらを含めることです。

これは正解であり、コマンド間でファイル名が渡されることはないため、投げることができるすべてのタイプのファイル名を処理します。

しかし、shopt nullglob答えは最良の答えです!


おそらく、もう一度答えるのではなく、元の答えを更新する必要があります。
qodeninja 2017

私が使用して考えてfind使用して対ls問題を解決するための2種類の方法があります。 find常にマシン上に存在するわけではありませんが、ls通常は存在します
mogsie

2
しかし、それからラードの箱にはfindおそらくそれらのすべての豪華なオプションがありませんls
tripleee

1
-maxdepth 1
tripleie

1
このソリューションでは、非表示のディレクトリ内のファイルもカウントされます。findデフォルトでこれを行います。これは、非表示の子フォルダーがあることに気付いていない場合に混乱を招く可能性がありls、デフォルトでは非表示のファイルを報告しない状況で使用すると便利な場合があります。
MrPotatoHead

6

これが私の1つのライナーです。

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)

理解するのに少しググる必要がありましたが、これは素晴らしいです!したがって、シェルプログラムに渡されたコマンドライン引数の数格納するset -- 準備ができている以外は何もしていません$#
xverges

@xvergesはい、「shopt -s nullglob」は隠しファイル(.files)を数えないためのものです。set-位置パラメータの数(この場合はファイルの数)を格納/設定するためのものです。#$は、位置パラメータ(ファイル数)の数を表示します。
zee

3

-Rオプションを使用して、再帰ディレクトリ内のファイルとともにファイルを見つけることができます

ls -R | wc -l // to find all the files

ls -R | grep log | wc -l // to find the files which contains the word log

あなたはgrepでパターンを使うことができます


3

重要なコメント

(コメントするには評判が足りません)

これはバギーです:

ls -1q some_pattern | wc -l

shopt -s nullglobたまたま設定された場合は、パターンを持つファイルだけでなく、すべての通常のファイルの数が出力されます(CentOS-8およびCygwinでテスト済み)。他の無意味なバグが何か知っている人はlsいますか?

これは正しく、はるかに高速です。

shopt -s nullglob; files=(some_pattern); echo ${#files[@]};

それは期待された仕事をします。


そして、実行時間は異なります。
1つ目:0.006CentOSと0.083Cygwin(注意して使用する場合)。
2番目:0.000CentOSと0.003Cygwin。


2

このようなコマンドは、シェル関数を使用して簡単に定義できます。このメソッドは、外部プログラムを必要とせず、子プロセスを生成しません。危険なことはしませんls解析を、「特殊」文字(空白、改行、バックスラッシュなど)を適切に処理します。シェルが提供するファイル名拡張メカニズムのみに依存します。少なくともsh、bash、zshと互換性があります。

以下の行は、呼び出されcountた引数の数を出力するという関数を定義しています。

count() { echo $#; }

必要なパターンを指定して呼び出すだけです。

count log*

グロビングパターンが一致しない場合に正しい結果を得るには、展開が発生するときにシェルオプションnullglob(またはfailglobzshのデフォルトの動作)を設定する必要があります。次のように設定できます。

shopt -s nullglob    # for sh / bash
setopt nullglob      # for zsh

何を数えたいかに応じて、シェルオプションにも興味があるかもしれませんdotglob

残念ながら、少なくともbashでは、これらのオプションをローカルで設定するのは簡単ではありません。それらをグローバルに設定したくない場合、最も簡単な解決策は、このより複雑な方法で関数を使用することです。

( shopt -s nullglob ; shopt -u failglob ; count log* )

軽量の構文を復元したい場合count log*、またはサブシェルの生成を避けたい場合は、次のようにハッキングすることができます。

# sh / bash:
# the alias is expanded before the globbing pattern, so we
# can set required options before the globbing gets expanded,
# and restore them afterwards.
count() {
    eval "$_count_saved_shopts"
    unset _count_saved_shopts
    echo $#
}
alias count='
    _count_saved_shopts="$(shopt -p nullglob failglob)"
    shopt -s nullglob
    shopt -u failglob
    count'

おまけとして、この関数はより一般的な使用法です。例えば:

count a* b*          # count files which match either a* or b*
count $(jobs -ps)    # count stopped jobs (sh / bash)

関数をスクリプトファイル(または同等のCプログラム)に変換することで、PATHから呼び出すことができ、findand などのプログラムで構成することもできますxargs

find "$FIND_OPTIONS" -exec count {} \+    # count results of a search

2

私はこの答えをたくさん考えました、特にdon't-parse-lsのものを考えると。最初に、私は試しました

<警告!うまくいきませんでした>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</警告!うまくいきませんでした>

これは、次のようなファイル名しかなかった場合に機能しました

touch $'w\nlf.aa'

このようなファイル名を作成すると失敗しました

touch $'firstline\n3 and some other\n1\n2\texciting\n86stuff.jpg'

私は最終的に私が下に置くものを思いつきました。注(サブディレクトリは含めずに)ディレクトリ内のすべてのファイルの数を取得しようとしていました。@Matと@Dan_Yardの回答に加えて、@ mogsieによって設定された要件の少なくともほとんどを持っていると思います(メモリについてはわかりません)。@ mogsieの回答は正しいと思います。しかし、それがls非常に特殊な状況でない限り、私は常に解析を避けようとします。

awk -F"\0" '{print NF-1}' < <(find . -maxdepth 1 -type f -print0) | awk '{sum+=$1}END{print sum}'

より読みやすく:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -print0) | \
    awk '{sum+=$1}END{print sum}'

これは、特にファイルの検索を実行し、出力をnull文字で区切って(スペースと改行の問題を回避するため)、null文字の数を数えます。末尾にヌル文字があるため、ファイルの数はヌル文字の数より1つ少なくなります。

OPの質問に答えるために、考慮すべき2つのケースがあります。

1)非再帰的検索:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

2)再帰的検索。-name少し異なる動作(隠しファイルなど)を使用するには、パラメーターの内容を変更する必要がある場合があります。

awk -F"\0" '{print NF-1}' < \
  <(find . -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

これらの回答が、この回答で述べた回答とどのように比較されるかについてコメントしたい場合は、どうぞ。


注、私はこの答えを得ながら、この思考プロセスにたどり着きました


1

これが私がいつもしていることです:

ls log * | awk 'END {print NR}'


awk 'END{print NR}'と同等である必要がありますwc -l
ムシフィル

0
ls -1 log* | wc -l

つまり、1行に1つのファイルをリストしてから、それをワードカウントコマンドにパイプし、パラメーターを行数に切り替えます。


ls出力をパイプする場合、「-1」オプションは必要ありません。ただし、パターンに一致するファイルがない場合は、lsエラーメッセージを非表示にすることができます。「ls log * 2> / dev / null | wc -l」をお勧めします。
JohnMudd、2014年

ダニエルの回答の下での議論はここでも関係があります。これは、一致するディレクトリまたは改行のあるファイル名がない場合に正常に機能しますが、適切な回答は少なくともこれらの境界条件を指摘し、優れた回答はそれらを持たないはずです。多くのバグは、誰かが理解できないコードをコピー/貼り付けたためです。したがって、欠陥を指摘することで、少なくとも注意すべき点を理解するのに役立ちます。(
当然の

-1

すべてをカウントするには、パイプlsをワードカウント行にパイプします。

ls | wc -l

パターンでカウントするには、最初にgrepにパイプします。

ls | grep log | wc -l
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.