Gibt es einen Bash-Befehl, der Dateien zählt?

182

Gibt es einen Bash-Befehl, der die Anzahl der Dateien zählt, die einem Muster entsprechen?

Zum Beispiel möchte ich die Anzahl aller Dateien in einem Verzeichnis ermitteln, die diesem Muster entsprechen: log*

hudi
quelle

Antworten:

241

Dieser einfache Einzeiler sollte in jeder Shell funktionieren, nicht nur in Bash:

ls -1q log* | wc -l

ls -1q gibt Ihnen eine Zeile pro Datei, auch wenn sie Leerzeichen oder Sonderzeichen wie Zeilenumbrüche enthalten.

Die Ausgabe wird an wc -l weitergeleitet, wodurch die Anzahl der Zeilen gezählt wird.

Daniel
quelle
10
Ich würde nicht verwenden -l, da dies stat(2)für jede Datei erforderlich ist und zum Zwecke des Zählens nichts hinzufügt.
Camh
12
Ich würde nicht verwenden ls, da es einen untergeordneten Prozess erstellt. log*wird durch die Shell nicht erweitert ls, so würde ein einfaches echoreichen.
Cdarke
2
Außer ein Echo funktioniert nicht, wenn Sie Dateinamen mit Leerzeichen oder Sonderzeichen haben.
Daniel
4
@WalterTross Das stimmt (nicht, dass Effizienz eine Anforderung der ursprünglichen Frage war). Ich habe auch gerade festgestellt, dass -q sich um Dateien mit Zeilenumbrüchen kümmert, auch wenn die Ausgabe nicht das Terminal ist. Und diese Flags werden von allen Plattformen und Shells unterstützt, auf denen ich getestet habe. Aktualisiere die Antwort, danke dir und camh für die Eingabe!
Daniel
3
Wenn logsin dem betreffenden Verzeichnis ein Verzeichnis aufgerufen wird, wird auch der Inhalt dieses Protokollverzeichnisses gezählt. Dies ist wahrscheinlich nicht beabsichtigt.
Mogsie
54

Sie können dies \nmit bash sicher tun (dh nicht von Dateien mit Leerzeichen oder in ihrem Namen abgehört werden ):

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

Sie müssen aktivieren, nullglobdamit Sie das Literal *.logim $logfiles Array nicht erhalten, wenn keine Dateien übereinstimmen. ( Beispiele zum sicheren Zurücksetzen eines 'set -x' finden Sie unter "Rückgängigmachen" eines 'set -x' .)

Matte
quelle
2
Vielleicht weist ausdrücklich darauf hin , dass dies ein Bash- ist nur Antwort, vor allem für neue Besucher , die noch nicht sind vollständig bis zu Geschwindigkeit auf der Differenz zwischen sh und bash
tripleee
Außerdem sollte das Finale shopt -u nullglobübersprungen werden, wenn nullglobes nicht deaktiviert wurde, als Sie begonnen haben.
Tripleee
Hinweis: Durch Ersetzen *.logdurch just *werden Verzeichnisse gezählt. Wenn die Dateien, die Sie aufzählen möchten, die traditionelle Namenskonvention haben name.extension, verwenden Sie *.*.
AlainD
51

Viele Antworten hier, aber einige berücksichtigen nicht

  • Dateinamen mit Leerzeichen, Zeilenumbrüchen oder Steuerzeichen
  • Dateinamen, die mit Bindestrichen beginnen (stellen Sie sich eine Datei mit dem Namen vor -l)
  • versteckte Dateien, die mit einem Punkt beginnen (wenn der Globus *.logstatt warlog*
  • Verzeichnisse, die mit dem Glob übereinstimmen (z. B. ein Verzeichnis mit dem Namen logs, das übereinstimmt log*)
  • leere Verzeichnisse (dh das Ergebnis ist 0)
  • extrem große Verzeichnisse (die Auflistung aller Verzeichnisse könnte den Speicher erschöpfen)

Hier ist eine Lösung, die alle von ihnen behandelt:

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

Erläuterung:

  • -Ubewirkt ls, dass die Einträge nicht sortiert werden, was bedeutet, dass nicht die gesamte Verzeichnisliste in den Speicher geladen werden muss
  • -bdruckt Escapezeichen im C-Stil für nicht grafische Zeichen, was entscheidend dazu führt, dass Zeilenumbrüche als gedruckt werden \n.
  • -adruckt alle Dateien aus, auch versteckte Dateien (nicht unbedingt erforderlich, wenn der Globus log*keine versteckten Dateien impliziert)
  • -ddruckt Verzeichnisse aus, ohne zu versuchen, den Inhalt des Verzeichnisses aufzulisten , was lsnormalerweise der Fall ist
  • -1 stellt sicher, dass es sich in einer Spalte befindet (ls tut dies automatisch, wenn in eine Pipe geschrieben wird, daher ist dies nicht unbedingt erforderlich)
  • 2>/dev/nullleitet stderr so um, dass bei 0 Protokolldateien die Fehlermeldung ignoriert wird. (Beachten Sie, dass shopt -s nullglobdies dazu führen würde, dass lsstattdessen das gesamte Arbeitsverzeichnis aufgelistet wird.)
  • wc -lverbraucht die Verzeichnisliste, während sie generiert wird, sodass sich die Ausgabe von lszu keinem Zeitpunkt im Speicher befindet.
  • --Dateinamen werden vom Befehl getrennt --, um nicht als Argumente für verstanden zu werden ls(falls log*entfernt)

Die Schale wird erweitern , log*um die vollständige Liste der Dateien, die Speicher erschöpfen kann , wenn es sich um eine Menge von Dateien ist, so dann durch grep läuft besser sein:

ls -Uba1 | grep ^log | wc -l

Letzteres verarbeitet extrem große Verzeichnisse von Dateien, ohne viel Speicher zu verbrauchen (obwohl es eine Subshell verwendet). Dies -dist nicht mehr erforderlich, da nur der Inhalt des aktuellen Verzeichnisses aufgelistet wird.

Mogsie
quelle
46

Für eine rekursive Suche:

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

wc -czählt die Anzahl der Zeichen in der Ausgabe von find, während -printf xangewiesen wird find, xfür jedes Ergebnis ein einzelnes zu drucken .

Führen Sie für eine nicht rekursive Suche Folgendes aus:

find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c
Will Vousden
quelle
6
Selbst wenn Sie keine Dateien mit Leerzeichen haben, kann ein anderer Benutzer Ihres Skripts auf eine Datei mit böswilligem Namen stoßen, wodurch die Skripte fehlschlagen. Andere Personen, die auf StackOverflow darauf stoßen, haben möglicherweise Dateien mit Zeilenumbrüchen und müssen die Fallstricke kennen.
Mogsie
Zu Ihrer Information, wenn Sie einfach weglassen, -name '*.log'werden alle Dateien gezählt, was ich für meinen Anwendungsfall benötigt habe. Auch das Flag -maxdepth ist äußerst nützlich, danke!
Starmandeluxe
2
Dies führt immer noch zu falschen Ergebnissen, wenn Dateinamen mit Zeilenumbrüchen enthalten sind. Die Problemumgehung ist einfach mit find; Drucken Sie einfach etwas anderes als den wörtlichen Dateinamen.
Tripleee
8

Die akzeptierte Antwort auf diese Frage ist falsch, aber ich habe eine geringe Wiederholungszahl und kann daher keinen Kommentar hinzufügen.

Die richtige Antwort auf diese Frage gibt Mat:

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

Das Problem mit der akzeptierten Antwort ist, dass wc -l die Anzahl der Zeilenumbruchzeichen zählt und diese auch dann zählt, wenn sie als '?' Auf dem Terminal gedruckt werden. in der Ausgabe von 'ls -l'. Dies bedeutet, dass die akzeptierte Antwort fehlschlägt, wenn ein Dateiname ein Zeilenumbruchzeichen enthält. Ich habe den vorgeschlagenen Befehl getestet:

ls -l log* | wc -l

und es wird fälschlicherweise der Wert 2 gemeldet, selbst wenn nur 1 Datei mit dem Muster übereinstimmt, dessen Name zufällig ein Zeilenumbruchzeichen enthält. Beispielsweise:

touch log$'\n'def
ls log* -l | wc -l
Dan Yard
quelle
6

Wenn Sie viele Dateien haben und die elegante shopt -s nullglobund Bash-Array-Lösung nicht verwenden möchten , können Sie find usw. verwenden, solange Sie den Dateinamen nicht ausdrucken (der möglicherweise Zeilenumbrüche enthält).

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

Dadurch werden alle Dateien gefunden, die mit log * übereinstimmen und nicht mit .*- beginnen. "Not name. *" Ist redundant, es ist jedoch wichtig zu beachten, dass die Standardeinstellung für "ls" darin besteht, keine Punktdateien anzuzeigen, sondern die Standardeinstellung denn zu finden ist, sie einzuschließen.

Dies ist eine korrekte Antwort und behandelt alle Arten von Dateinamen, die Sie darauf werfen können, da der Dateiname niemals zwischen Befehlen weitergegeben wird.

Aber die shopt nullglobAntwort ist die beste Antwort!

Mogsie
quelle
Sie sollten wahrscheinlich Ihre ursprüngliche Antwort aktualisieren, anstatt erneut zu antworten.
Qodeninja
Ich denke, mit findvs vs lssind zwei verschiedene Möglichkeiten, das Problem zu lösen. findist nicht immer auf einer Maschine vorhanden, ist aber lsnormalerweise,
Mogsie
2
Aber eine Schachtel Schmalz, die es findwahrscheinlich nicht gibt, hat auch nicht all diese ausgefallenen Optionen ls.
Tripleee
1
Beachten Sie auch, wie sich dies auf einen ganzen Verzeichnisbaum erstreckt, wenn Sie den-maxdepth 1
Tripleee
1
Beachten Sie, dass diese Lösung Dateien in versteckten Verzeichnissen in ihrer Anzahl zählt. findmacht dies standardmäßig. Dies kann zu Verwirrung führen, wenn man nicht merkt, dass es einen versteckten untergeordneten Ordner gibt, und es kann unter lsbestimmten Umständen vorteilhaft sein, ihn zu verwenden , der standardmäßig keine versteckten Dateien meldet.
MrPotatoHead
6

Hier ist mein Einzeiler dafür.

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
zee
quelle
Ich musste ein bisschen googeln, um zu verstehen, aber das ist schön! Es set -- wird also nichts anderes getan, als uns darauf $#
vorzubereiten
@xverges Ja, "shopt -s nullglob" dient zum Nichtzählen versteckter Dateien (.files). set - dient zum Speichern / Einstellen der Anzahl der Positionsparameter (in diesem Fall der Anzahl der Dateien). und # $ zum Anzeigen der Anzahl der Positionsparameter (Anzahl der Dateien).
Zee
2

Mit der Option -R können Sie die Dateien zusammen mit denen in den rekursiven Verzeichnissen suchen

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

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

Sie können Muster auf dem Grep verwenden

Moh .S
quelle
2

Ich habe über diese Antwort viel nachgedacht, besonders angesichts der Dinge , die man nicht analysiert . Zuerst habe ich es versucht

<WARNUNG! Hat nicht funktioniert>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</ WARNUNG! Hat nicht funktioniert>

was funktionierte, wenn es nur einen Dateinamen wie gab

touch $'w\nlf.aa'

aber fehlgeschlagen, wenn ich einen Dateinamen wie diesen gemacht habe

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

Ich habe mir endlich ausgedacht, was ich unten schreibe. Hinweis Ich habe versucht, alle Dateien im Verzeichnis zu ermitteln (ohne Unterverzeichnisse). Ich denke, zusammen mit den Antworten von @Mat und @Dan_Yard sowie den meisten Anforderungen von @mogsie (ich bin mir nicht sicher, was das Gedächtnis betrifft.) Ich denke, die Antwort von @mogsie ist richtig. Aber ich versuche immer, mich vom Parsen fernzuhalten, lses sei denn, es handelt sich um eine äußerst spezifische Situation.

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

Lesbarer:

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

Hierbei wird eine Suche speziell für Dateien durchgeführt, wobei die Ausgabe durch ein Nullzeichen begrenzt wird (um Probleme mit Leerzeichen und Zeilenvorschüben zu vermeiden) und anschließend die Anzahl der Nullzeichen gezählt wird. Die Anzahl der Dateien ist eins weniger als die Anzahl der Nullzeichen, da am Ende ein Nullzeichen steht.

Um die Frage des OP zu beantworten, sind zwei Fälle zu berücksichtigen

1) Nicht rekursive Suche:

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

2) Rekursive Suche. Beachten Sie, dass der Inhalt des -nameParameters möglicherweise geändert werden muss, um ein etwas anderes Verhalten zu erzielen (versteckte Dateien usw.).

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

Wenn jemand kommentieren möchte, wie diese Antworten mit denen verglichen werden, die ich in dieser Antwort erwähnt habe, tun Sie dies bitte.


Beachten Sie, dass ich zu diesem Gedankenprozess gekommen bin, als ich diese Antwort erhalten habe .

bballdave025
quelle
2

Ein wichtiger Kommentar

(nicht genug Ruf, um zu kommentieren)

Das ist BUGGY :

ls -1q some_pattern | wc -l

Wenn shopt -s nullglobdies festgelegt ist, wird die Anzahl ALLER regulären Dateien gedruckt , nicht nur derjenigen mit dem Muster (getestet unter CentOS-8 und Cygwin). Wer weiß, welche anderen bedeutungslosen Fehler es lsgibt?

Das ist RICHTIG und viel schneller:

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

Es macht den erwarteten Job.


Und die Laufzeiten sind unterschiedlich.
Das erste: 0.006unter CentOS und 0.083unter Cygwin (falls es mit Vorsicht verwendet wird).
Der 2.: 0.000auf CentOS und 0.003auf Cygwin.

Kleiner Junge
quelle
1

Folgendes mache ich immer:

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

Shuang Liang
quelle
awk 'END{print NR}'sollte gleichbedeutend sein mit wc -l.
Musiphil
1

Sie können einen solchen Befehl einfach mithilfe einer Shell-Funktion definieren. Diese Methode erfordert kein externes Programm und erzeugt keinen untergeordneten Prozess. Es wird kein gefährliches lsParsen versucht und „Sonderzeichen“ (Leerzeichen, Zeilenumbrüche, Backslashes usw.) werden problemlos verarbeitet. Es basiert nur auf dem von der Shell bereitgestellten Mechanismus zur Erweiterung des Dateinamens. Es ist kompatibel mit mindestens sh, bash und zsh.

Die folgende Zeile definiert eine aufgerufene Funktion, countdie die Anzahl der Argumente ausgibt, mit denen sie aufgerufen wurde.

count() { echo $#; }

Nennen Sie es einfach mit dem gewünschten Muster:

count log*

Damit das Ergebnis korrekt ist, wenn das Globbing-Muster nicht übereinstimmt, muss die Shell-Option nullglob(oder failglob- das ist das Standardverhalten bei zsh) zum Zeitpunkt der Erweiterung festgelegt werden. Es kann wie folgt eingestellt werden:

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

Je nachdem, was Sie zählen möchten, könnte Sie auch die Shell-Option interessieren dotglob.

Leider ist es zumindest mit bash nicht einfach, diese Optionen lokal festzulegen. Wenn Sie sie nicht global festlegen möchten, besteht die einfachste Lösung darin, die Funktion auf diese kompliziertere Weise zu verwenden:

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

Wenn Sie die leichtgewichtige Syntax wiederherstellen möchten count log*oder wenn Sie wirklich vermeiden möchten, dass eine Unterschale erzeugt wird, können Sie Folgendes hacken:

# 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'

Als Bonus ist diese Funktion allgemeiner. Zum Beispiel:

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

Durch Verwandeln der Funktion in eine Skriptdatei (oder ein gleichwertiges C-Programm), die über PATH aufgerufen werden kann, kann sie auch mit Programmen wie findund zusammengesetzt werden xargs:

find "$FIND_OPTIONS" -exec count {} \+    # count results of a search
Maëlan
quelle
0
ls -1 log* | wc -l

Dies bedeutet, dass eine Datei pro Zeile aufgelistet und dann an den Wortzählbefehl weitergeleitet wird, wobei der Parameter auf die Anzahl der Zeilen umgeschaltet wird.

Nudzo
quelle
Die Option "-1" ist nicht erforderlich, wenn der ls-Ausgang weitergeleitet wird. Möglicherweise möchten Sie jedoch die Fehlermeldung ausblenden, wenn keine Datei mit dem Muster übereinstimmt. Ich schlage "ls log * 2> / dev / null | wc -l" vor.
JohnMudd
Die Diskussion unter Daniels Antwort ist auch hier relevant. Dies funktioniert gut, wenn Sie keine übereinstimmenden Verzeichnisse oder Dateinamen mit Zeilenumbrüchen haben, aber eine gute Antwort sollte zumindest auf diese Randbedingungen hinweisen, und eine gute Antwort sollte sie nicht haben. Viele Fehler sind darauf zurückzuführen, dass jemand Code kopiert / eingefügt hat, den er nicht verstanden hat. Wenn sie also zumindest auf die Mängel hinweisen, können sie besser verstehen, worauf sie achten müssen. (Zugegeben, es passieren viel mehr Fehler, weil sie die Vorbehalte ignoriert haben und sich dann die Dinge geändert haben, nachdem sie dachten, der Code sei wahrscheinlich gut genug für ihren Zweck.)
Tripleee
-1

Um alles zu zählen, leiten Sie ls einfach zur Wortzählzeile:

ls | wc -l

Um mit dem Muster zu zählen, leiten Sie zuerst an grep weiter:

ls | grep log | wc -l
jturi
quelle