Wie zählt man die Gesamtzahl der Zeilen aller TXT-Dateien?

Wie zählt man die Gesamtzahl der Zeilen aller TXT-Dateien?

Ich versuche herauszufinden, wie ich die Gesamtzahl der Zeilen aus allen TXT-Dateien ermitteln kann. Ich glaube, das Problem liegt in Zeile 6 -> let $((total = total + count )). Weiß jemand, wie man das korrigieren kann?

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

Danke

Antwort1

Ihre Zeile 6 wird besser geschrieben als

total=$(( total + count ))

... aber noch besser wäre es, ein Werkzeug zu verwenden, dasgemachtzum Zählen von Zeilen (vorausgesetzt, Sie möchten Zeilenumbrüche zählen, also die Anzahl der ordnungsgemäß beendeten Zeilen)

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

Dadurch werden alle regulären Dateien im aktuellen Verzeichnis oder darunter gefunden, deren Dateinamen auf enden .txt. Alle diese Dateien werden zu einem einzigen Stream verkettet und an weitergeleitet. Das wc -lProgramm gibt die Gesamtzahl der Zeilen aus, die im Titel und Text der Frage angegeben ist.

Vollständiges Skript:

#!/bin/sh

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

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

Um auch die Zeilenanzahl der einzelnen Dateien zu erhalten, beachten Sie

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'

Dies ruft wc -lmehrere Dateien auf und gibt die Zeilenanzahl für jede einzelne Datei aus. Wenn wc -les mit mehr als einem Dateinamen aufgerufen wird, wird am Ende eine Zeile mit der Gesamtanzahl ausgegeben. Wir löschen diese Zeile, sedwenn das Inline- sh -cSkript mit mehr als einem Dateinamenargument aufgerufen wird.

Die lange Liste der Zeilenanzahlen und Dateipfadnamen wird dann an übergeben awk, das die Anzahlen einfach addiert (und die Daten weiterleitet) und dem Benutzer am Ende die Gesamtanzahl präsentiert.


Auf GNU-Systemen wckann das Tool Pfadnamen aus einem durch Nullen getrennten Datenstrom lesen. Sie können es findund seine -print0Aktion auf diesen Systemen wie folgt verwenden:

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

Hier werden die gefundenen Pfadnamen als durch Nullen getrennte Liste über die Pipe an übergeben, wobei wcdie nicht standardmäßige Option verwendet wird -print0. Das wcDienstprogramm wird mit der nicht standardmäßigen --files0-fromOption verwendet, um die über die Pipe übergebene Liste zu lesen.

Antwort2

let $((total = total + count ))

Dies funktioniert, ist jedoch etwas überflüssig, da sowohl letals auch $(( .. ))die arithmetische Erweiterung starten.

Jeder von let "total = total + count", let "total += count", : $((total = total + count))oder total=$((total + count))würde es ohne Duplizierung tun. Die letzten beiden sollten mit einer Standard-Shell kompatibel sein, letsind es aber nicht.

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

Sie haben nicht gesagt, welches Problem Sie meinen, aber ein Problem, das Sie hier haben, ist, dass in Bash die Teile einer Pipeline standardmäßig in Subshells ausgeführt werden, sodass alle totalinnerhalb der whileSchleife vorgenommenen Änderungen danach nicht sichtbar sind. Siehe:Warum ist meine Variable in einer „while read“-Schleife lokal, aber nicht in einer anderen, scheinbar ähnlichen Schleife?

Sie können shopt -s lastpipeden letzten Teil der Pipeline in der Shell ausführen lassen oder die whileund gruppieren echo:

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

Natürlich find ... | while read -r FILE;wird es Probleme mit Dateinamen geben, die Zeilenumbrüche enthalten oder mit Leerzeichen beginnen/enden. Sie können das beheben mit

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

oder, wenn Ihnen die Aufschlüsselung der Zeilenanzahl pro Datei egal ist und Sie wissen, dass es sich bei Ihren Dateien um vollständige Textdateien handelt und in keiner die letzte neue Zeile fehlt, können Sie einfach alle Dateien aneinanderreihen und die Ausführung wc -ldarauf aufbauen.

Wenn in Ihren Dateien möglicherweise der Zeilenumbruch am Ende der letzten Zeile fehlt und Sie diese letzte unvollständige Zeile zählen möchten, können Sie das nicht tun und müssen weiterhin grep -c ^anstelle von verwenden wc -l. (Das Zählen der letzten unvollständigen Zeile ist so ziemlich der einzige Grund, grep -c ^anstelle von zu verwenden wc -l.)

Sehen:Welchen Sinn hat es, am Ende einer Datei eine neue Zeile hinzuzufügen?UndWarum sollten Textdateien mit einer neuen Zeile enden?auf SO.

Wenn Sie außerdem nur die Gesamtanzahl möchten, alle Dateien, die dem Muster entsprechen, normale Dateien sind (der -type fTest kann also weggelassen werden) und Sie über Bash und GNU grep verfügen, können Sie auch Folgendes tun:

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

**/*.txtist ein rekursiver Glob, er muss explizit aktiviert werden, um zu funktionieren. dotglobsorgt dafür, dass dieser Glob auch Dateinamen abgleicht, die mit einem Punkt beginnen. grep -hunterdrückt die Dateinamen aus der Ausgabe und das awkSkript zählt die Summe. Da keine Dateinamen gedruckt werden, sollte dies funktionieren, auch wenn einige davon problematisch sind.

Oder, wie von @fra-san vorgeschlagen, basierend auf einer anderen, inzwischen gelöschten Antwort:

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

Antwort3

let total+=count$(( ))funktionieren wird, ist bei dieser Form der arithmetischen Auswertung nicht erforderlich .

Aber Sie wären viel besser dran, wenn Sie dies mit tun würden wc -l.

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

Wenn Sie eine benutzerdefinierte Ausgabe wie in Ihrem Shell-Skript oben wünschen, ODER wenn es wahrscheinlich mehr Dateinamen gibt, als in die Zeilenlängenbeschränkung von ~2 MB von Bash unter Linux passen, können Sie awkoder perlzum Zählen verwenden. Alles ist besser als eine Shell-While-Read-Schleife (sieheWarum gilt die Verwendung einer Shell-Schleife zur Textverarbeitung als schlechte Praxis?). Zum Beispiel:

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
  }' {} +

Hinweis: Der find ... -exec perlobige Befehl ignoriert leere Dateien, während die wc -lVersion sie mit einer Zeilenanzahl von 0 auflisten würde. Es ist möglich, Perl dazu zu bringen, dasselbe zu tun (siehe unten).

Andererseits wird eine Zeilenzählung und eine Gesamtsumme fürbeliebigAnzahl der Dateien, auch wenn sie nicht alle in eine Shell-Befehlszeile passen - die wc -lVersion würde ausgebenzweioder in diesem Fall mehr totalZeilen – wird wahrscheinlich nicht passieren, ist aber auch nicht das, was Sie wollen, wenn es passiert.

Dies sollte funktionieren. Dabei wird wc -ldie Ausgabe verwendet und an Perl weitergeleitet, um sie in das gewünschte Ausgabeformat zu ändern:

$ 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"}'

Antwort4

Versuche dies:

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

verwandte Informationen