Antworten:
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.
ls
, da es einen untergeordneten Prozess erstellt. log*
wird durch die Shell nicht erweitert ls
, so würde ein einfaches echo
reichen.
logs
in dem betreffenden Verzeichnis ein Verzeichnis aufgerufen wird, wird auch der Inhalt dieses Protokollverzeichnisses gezählt. Dies ist wahrscheinlich nicht beabsichtigt.
Sie können dies \n
mit 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, nullglob
damit Sie das Literal *.log
im $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' .)
shopt -u nullglob
übersprungen werden, wenn nullglob
es nicht deaktiviert wurde, als Sie begonnen haben.
*.log
durch just *
werden Verzeichnisse gezählt. Wenn die Dateien, die Sie aufzählen möchten, die traditionelle Namenskonvention haben name.extension
, verwenden Sie *.*
.
Viele Antworten hier, aber einige berücksichtigen nicht
-l
)*.log
statt warlog*
logs
, das übereinstimmt log*
)Hier ist eine Lösung, die alle von ihnen behandelt:
ls 2>/dev/null -Ubad1 -- log* | wc -l
Erläuterung:
-U
bewirkt ls
, dass die Einträge nicht sortiert werden, was bedeutet, dass nicht die gesamte Verzeichnisliste in den Speicher geladen werden muss-b
druckt Escapezeichen im C-Stil für nicht grafische Zeichen, was entscheidend dazu führt, dass Zeilenumbrüche als gedruckt werden \n
.-a
druckt alle Dateien aus, auch versteckte Dateien (nicht unbedingt erforderlich, wenn der Globus log*
keine versteckten Dateien impliziert)-d
druckt Verzeichnisse aus, ohne zu versuchen, den Inhalt des Verzeichnisses aufzulisten , was ls
normalerweise 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/null
leitet stderr so um, dass bei 0 Protokolldateien die Fehlermeldung ignoriert wird. (Beachten Sie, dass shopt -s nullglob
dies dazu führen würde, dass ls
stattdessen das gesamte Arbeitsverzeichnis aufgelistet wird.)wc -l
verbraucht die Verzeichnisliste, während sie generiert wird, sodass sich die Ausgabe von ls
zu 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 -d
ist nicht mehr erforderlich, da nur der Inhalt des aktuellen Verzeichnisses aufgelistet wird.
Für eine rekursive Suche:
find . -type f -name '*.log' -printf x | wc -c
wc -c
zählt die Anzahl der Zeichen in der Ausgabe von find
, während -printf x
angewiesen wird find
, x
fü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
-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!
find
; Drucken Sie einfach etwas anderes als den wörtlichen Dateinamen.
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
Wenn Sie viele Dateien haben und die elegante shopt -s nullglob
und 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 nullglob
Antwort ist die beste Antwort!
find
vs vs ls
sind zwei verschiedene Möglichkeiten, das Problem zu lösen. find
ist nicht immer auf einer Maschine vorhanden, ist aber ls
normalerweise,
find
wahrscheinlich nicht gibt, hat auch nicht all diese ausgefallenen Optionen ls
.
-maxdepth 1
find
macht dies standardmäßig. Dies kann zu Verwirrung führen, wenn man nicht merkt, dass es einen versteckten untergeordneten Ordner gibt, und es kann unter ls
bestimmten Umständen vorteilhaft sein, ihn zu verwenden , der standardmäßig keine versteckten Dateien meldet.
Hier ist mein Einzeiler dafür.
file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
set --
wird also nichts anderes getan, als uns darauf $#
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, ls
es 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 -name
Parameters 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 .
(nicht genug Ruf, um zu kommentieren)
Das ist BUGGY :
ls -1q some_pattern | wc -l
Wenn shopt -s nullglob
dies 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 ls
gibt?
Das ist RICHTIG und viel schneller:
shopt -s nullglob; files=(some_pattern); echo ${#files[@]};
Es macht den erwarteten Job.
0.006
unter CentOS und 0.083
unter Cygwin (falls es mit Vorsicht verwendet wird).
0.000
auf CentOS und 0.003
auf Cygwin.
Folgendes mache ich immer:
ls log * | awk 'END {print NR}'
awk 'END{print NR}'
sollte gleichbedeutend sein mit wc -l
.
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 ls
Parsen 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, count
die 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 find
und zusammengesetzt werden xargs
:
find "$FIND_OPTIONS" -exec count {} \+ # count results of a search
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.
-l
, da diesstat(2)
für jede Datei erforderlich ist und zum Zwecke des Zählens nichts hinzufügt.