
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 -l
Programm 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 -l
mehrere Dateien auf und gibt die Zeilenanzahl für jede einzelne Datei aus. Wenn wc -l
es mit mehr als einem Dateinamen aufgerufen wird, wird am Ende eine Zeile mit der Gesamtanzahl ausgegeben. Wir löschen diese Zeile, sed
wenn das Inline- sh -c
Skript 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 wc
kann das Tool Pfadnamen aus einem durch Nullen getrennten Datenstrom lesen. Sie können es find
und seine -print0
Aktion 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 wc
die nicht standardmäßige Option verwendet wird -print0
. Das wc
Dienstprogramm wird mit der nicht standardmäßigen --files0-from
Option verwendet, um die über die Pipe übergebene Liste zu lesen.
Antwort2
let $((total = total + count ))
Dies funktioniert, ist jedoch etwas überflüssig, da sowohl let
als 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, let
sind 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 total
innerhalb der while
Schleife 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 lastpipe
den letzten Teil der Pipeline in der Shell ausführen lassen oder die while
und 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 -l
darauf 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 f
Test 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 }'
**/*.txt
ist ein rekursiver Glob, er muss explizit aktiviert werden, um zu funktionieren. dotglob
sorgt dafür, dass dieser Glob auch Dateinamen abgleicht, die mit einem Punkt beginnen. grep -h
unterdrückt die Dateinamen aus der Ausgabe und das awk
Skript 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 awk
oder perl
zum 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 perl
obige Befehl ignoriert leere Dateien, während die wc -l
Version 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 -l
Version würde ausgebenzweioder in diesem Fall mehr total
Zeilen – wird wahrscheinlich nicht passieren, ist aber auch nicht das, was Sie wollen, wenn es passiert.
Dies sollte funktionieren. Dabei wird wc -l
die 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}