すべての .txt ファイルの合計行数をカウントするにはどうすればよいですか?

すべての .txt ファイルの合計行数をカウントするにはどうすればよいですか?

すべての .txt ファイルから行の合計数を取得する方法を見つけようとしています。問題は 6 行目にあると思いますlet $((total = total + count ))。これを正しく記述する方法を知っている人はいますか?

#!/bin/bash
total=0
find /home -type f -name "*.txt" | while read -r FILE; do
          count=$(grep -c ^ < "$FILE")
           echo "$FILE has $count lines"
           let $((total = total + count ))
        done
        echo TOTAL LINES COUNTED:  $total

ありがとう

答え1

6行目は次のように書くほうがよいでしょう

total=$(( total + count ))

...しかし、さらに良いのは、作った行数を数える(改行、つまり適切に終了した行の数を数えると仮定)

find . -name '*.txt' -type f -exec cat {} + | wc -l

これは、現在のディレクトリ内またはその下にある、ファイル名が で終わるすべての通常ファイルを検索します.txt。これらのファイルはすべて 1 つのストリームに連結され、 にパイプされwc -l、質問のタイトルとテキストで要求されている行の合計数を出力します。

完全なスクリプト:

#!/bin/sh

nlines=$( find . -name '*.txt' -type f -exec cat {} + | wc -l )

printf 'Total number of lines: %d\n' "$nlines"

個々のファイルの行数も取得するには、次のようにします。

find . -name '*.txt' -type f -exec sh -c '
    wc -l "$@" |
    if [ "$#" -gt 1 ]; then
        sed "\$d"
    else
        cat
    fi' sh {} + |
awk '{ tot += $1 } END { printf "Total: %d\n", tot }; 1'

これはwc -lファイルのバッチを呼び出し、各ファイルの行数を出力します。 がwc -l複数のファイル名で呼び出されると、合計数を含む行が最後に出力されます。sedインラインsh -cスクリプトが複数のファイル名引数で呼び出された場合は、 でこの行を削除します。

行数とファイル パス名の長いリストは に渡されawk、 は単純に行数を合計し (データを渡し)、最後に合計数をユーザーに提示します。


GNU システムでは、wcツールはヌル区切りのストリームからパス名を読み取ることができます。これらのシステムでは、次のようにfindとそのアクションを使用できます。-print0

find . -name '*.txt' -type f -print0 |
wc --files0-from=- -l

wcここで、見つかったパス名は、非標準を使用して、パイプを介してヌル区切りのリストとして渡されます-print0。ユーティリティは、パイプを介して渡されるリストを読み取るために、wc非標準オプションとともに使用されます。--files0-from

答え2

let $((total = total + count ))

letこれは機能しますが、 と の両方が$(( .. ))算術展開を開始するため、少し冗長です。

let "total = total + count"、、またはのいずれかを使用するとlet "total += count"、重複せずに実行できます。最後の 2 つは標準シェルと互換性があるはずですが、そうではありません。: $((total = total + count))total=$((total + count))let

total=0
find /home -type f -name "*.txt" | while read -r FILE; do
    total=...
done
echo TOTAL LINES COUNTED:  $total

どのような問題を意味しているのかは述べていませんが、ここで問題となるのは、Bash ではパイプラインの各部分がデフォルトでサブシェルで実行されるため、ループtotal内で行われた変更はwhileループ後には表示されないという点です。以下を参照してください。変数が 1 つの 'while read' ループではローカルであるのに、他の類似したループではローカルではないのはなぜですか?

shopt -s lastpipeパイプラインの最後の部分をシェルで実行するには を使用します。またはwhileと をグループ化しますecho

find ... | { while ...
    done; echo "$total"; }

もちろん、find ... | while read -r FILE;改行文字を含むファイル名や、空白で始まる/終わるファイル名には問題があります。これを修正するには、

find ... -print0 | while IFS= read -r -d '' FILE; do ...

または、ファイルごとの行数の内訳を気にせず、ファイルが完全なテキスト ファイルであり、最後の改行が欠落していないことがわかっている場合は、すべてのファイルを連結して実行するwc -lこともできます。

ファイルの最後の行の末尾に改行が欠落している可能性があり、その不完全な最終行をカウントしたい場合、それはできないので、grep -c ^の代わりにを使用し続ける必要があります。(の代わりにwc -lを使用する理由は、最後の不完全な行をカウントすることだけです。)grep -c ^wc -l

見る:ファイルの末尾に新しい行を追加する意味は何でしょうか?そしてテキストファイルはなぜ改行で終わる必要があるのでしょうか?SO で。

また、合計数だけが必要な場合、パターンに一致するすべてのファイルが通常のファイルである (したがってテスト-type fを省略できる)、および Bash と GNU grep がある場合は、次のようにすることもできます。

shopt -s globstar
shopt -s dotglob
grep -h -c ^ **/*.txt | awk '{ a += $0 } END { print a }'

**/*.txtは再帰的なグロブなので、動作させるには明示的に有効にする必要があります。dotglobグロブがドットで始まるファイル名にも一致するようにします。grep -h出力からファイル名を抑制し、awkスクリプトが合計をカウントします。ファイル名は印刷されないので、一部に問題がある場合でも、これは動作するはずです。

または、@fra-san が提案したように、現在は削除されている別の回答に基づいています。

grep -r -c -h --include='*.sh' ^ |awk '{ a+= $0 } END {print a }'

答え3

let total+=count$(( ))は機能しますが、この形式の算術評価では必要ありません。

しかし、これを実行する方がはるかに良いでしょうwc -l

find /home -type f -name '*.txt' -exec wc -l {} +

上記のシェルスクリプトのようにカスタム出力が必要な場合、またはLinuxのbashの約2MBの行の長さ制限に収まらないファイル名が多い場合は、awkまたはを使用しperlてカウントを行うことができます。シェルのwhile-readループよりも優れています(シェル ループを使用してテキストを処理するのはなぜ悪い習慣だと考えられるのでしょうか?)。 例えば:

find /home -type f -name '*.txt' -exec perl -lne '
  $files{$ARGV}++;

  END {
    foreach (sort keys %files) {
      printf "%s has %s lines\n", $_, $files{$_};
      $total+=$files{$_}
    };
    printf "TOTAL LINES COUNTED: %s\n", $total
  }' {} +

注意:find ... -exec perl上記のコマンドは空のファイルを無視しますが、wc -lバージョンでは行数 0 でそれらをリストします。 perl で同じことを実行することも可能です (以下を参照)。

一方、行数と合計を計算しますどれでもファイルの数、たとえそれらが全て1つのシェルのコマンドラインに収まらないとしても、wc -lバージョンは印刷されますまたは、その場合は行数が増えるtotal可能性があります。おそらく発生しないでしょうが、発生した場合は望んでいる結果にはなりません。

これは動作するはずです。これを使用してwc -l出力を perl にパイプし、目的の出力形式に変更します。

$ find /home -type f -name '*.txt' -exec wc -l {} + |
    perl -lne 'next if m/^\s+\d+\s+total$/;
               s/\s+(\d+)\s+(.*)/$2 has $1 lines/;
               print;
               $total += $1;

               END { print "TOTAL LINES COUNTED:  $total"}'

答え4

これを試して:

#!/bin/bash
export total=$(find . -name '*.txt' -exec wc -l "{}" ";" | awk 'BEGIN{sum=0} {sum+=$1} END{print sum}')
echo TOTAL LINES COUNTED ${total}

関連情報