Wie melde ich die Anzahl der Dateien in allen Unterverzeichnissen?

24

Ich muss alle Unterverzeichnisse überprüfen und angeben, wie viele Dateien sie enthalten (ohne weitere Rekursion):

directoryName1 numberOfFiles
directoryName2 numberOfFiles
Schüchterner Junge
quelle
Warum möchten Sie verwenden, findwenn Bash dies tut? (shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done): Zählen Sie für alle Verzeichnisse die Anzahl der Einträge in diesem Verzeichnis (einschließlich versteckter ...
Punktedateien
@janmoesen Warum hast du das nicht beantwortet? Ich bin neu in der Shell-Skripterstellung, kann aber keine Fallstricke mit Ihrer Methode erkennen. Für mich sieht es nach dem besten Weg aus. Niemand hat Ihren Kommentar positiv bewertet, aber niemand hat kommentiert, warum er auch schlecht sein könnte. Die überstimmten Antworten haben viel mehr Wiederholungen als Sie, daher frage ich mich, ob mir etwas fehlt.
Toxalot
@toxalot: Ich habe mich nicht darum gekümmert, es als Antwort hinzuzufügen, weil es so kurz war (und möglicherweise etwas herablassend im Ton). Fühlen Sie sich frei, um den Kommentar zu stimmen. :-) Außerdem ist die Frage etwas vage, was "wie viele Dateien" bedeutet. Meine Lösung zählt "normale" Dateien und Verzeichnisse. Vielleicht bedeutete das Plakat wirklich "Dateien, keine Verzeichnisse". Eine andere Sache, die zu beachten ist, ist, dass dieses Globbing "versteckte" Punktdateien nicht berücksichtigt. Es gibt jedoch Möglichkeiten, diese beiden Fallstricke zu umgehen. Aber auch hier gilt: Ich bin mir nicht sicher, welche Anforderungen der ursprüngliche Poster genau erfüllt.
Januar

Antworten:

30

Dies geschieht auf sichere und tragbare Weise. Es wird nicht durch seltsame Dateinamen verwechselt.

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \; | wc -l && echo $f; done

Beachten Sie, dass zuerst die Anzahl der Dateien und dann der Verzeichnisname in einer separaten Zeile ausgegeben werden. Wenn Sie das OP-Format beibehalten möchten, müssen Sie weitere Formatierungen vornehmen, z

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \;|wc -l|tr '\n' ' ' && echo $f; done|awk '{print $2"\t"$1}'

Wenn Sie bestimmte Unterverzeichnisse haben, an denen Sie interessiert sind, können Sie diese ersetzen *.

Warum ist das sicher? (und daher script-würdig)

Dateinamen können außer beliebige Zeichen enthalten /. Es gibt einige Zeichen, die entweder von der Shell oder von den Befehlen speziell behandelt werden. Dazu gehören Leerzeichen, Zeilenumbrüche und Bindestriche.

Die Verwendung des for f in *Konstrukts ist eine sichere Methode, um jeden Dateinamen abzurufen, unabhängig davon, was er enthält.

Sobald Sie den Dateinamen in einer Variablen haben, müssen Sie noch Dinge wie vermeiden find $f. Wenn $fder Dateiname enthalten ist -test, findwürde ich mich über die Option beschweren, die Sie gerade angegeben haben. Die Möglichkeit, dies zu vermeiden, besteht in der Verwendung ./vor dem Namen. Auf diese Weise hat es die gleiche Bedeutung, aber es beginnt nicht mehr mit einem Bindestrich.

Zeilenumbrüche und Leerzeichen sind ebenfalls ein Problem. Wenn $f"Hallo, Kumpel" als Dateiname enthalten ist find ./$f, ist find ./hello, buddy. Du sagst findzu sehen ./hello,und buddy. Wenn diese nicht existieren, wird es sich beschweren und niemals hineinschauen ./hello, buddy. Dies lässt sich leicht vermeiden - verwenden Sie Anführungszeichen um Ihre Variablen.

Schließlich können Dateinamen Zeilenumbrüche enthalten, sodass das Zählen von Zeilenumbrüchen in einer Liste von Dateinamen nicht funktioniert. Für jeden Dateinamen mit einer neuen Zeile erhalten Sie eine zusätzliche Anzahl. Um dies zu vermeiden, zählen Sie keine Zeilenumbrüche in einer Dateiliste. Zählen Sie stattdessen Zeilenumbrüche (oder andere Zeichen), die eine einzelne Datei darstellen. Deshalb hat der findBefehl einfach -exec echo \;und nicht -exec echo {} \;. Ich möchte nur eine einzige neue Zeile drucken, um die Dateien zu zählen.

Shawn J. Goff
quelle
1
Warum gibt es eine Person auf der Welt, die Zeilenumbrüche im Dateinamen verwendet? Danke für die Antwort.
ShyBoy
1
Dateinamen können alle Zeichen außer / und das Nullzeichen enthalten, glaube ich. dwheeler.com/essays/fixing-unix-linux-filenames.html
Flimm
2
Die Zählung schließt das Verzeichnis selbst ein. Wenn Sie das von der Zählung ausschließen möchten, verwenden Sie-mindepth 1
toxalot
Sie könnten auch -printf '\n'anstelle von verwenden -exec echo.
Toxalot
1
@toxalot können Sie, wenn Sie einen Find haben, der dies unterstützt -printf, aber nicht, wenn Sie möchten, dass es zum Beispiel unter FreeBSD funktioniert.
Shawn J. Goff
6

Unter der Annahme, dass Sie nach einer Standard-Linux-Lösung suchen, können Sie dies relativ einfach erreichen mit find:

find dir1/ dir2/ -maxdepth 1 -type f | wc -l

Wobei finddie beiden angegebenen Unterverzeichnisse durchlaufen werden, bis zu einem -maxdepthvon 1, was eine weitere Rekursion verhindert und nur Dateien ( -type f) meldet, die durch Zeilenumbrüche getrennt sind. Das Ergebnis wird dann weitergeleitet, wcum die Anzahl dieser Zeilen zu zählen.

jasonwryan
quelle
Ich habe mehr als 2 Verzeichnisse ... Wie kann ich Ihren Befehl mit der find . -maxdepth 1 -type dAusgabe kombinieren ?
ShyBoy
Sie können entweder (a) die erforderlichen Verzeichnisse in eine Variable aufnehmen und find $dirs ...(b), wenn sie sich ausschließlich in dem einen übergeordneten Verzeichnis befinden, Glob aus diesem Verzeichnis,find */ ...
jasonwryan 23.10.11
1
Dies meldet falsche Ergebnisse, wenn ein Dateiname ein Zeilenvorschubzeichen enthält.
Shawn J. Goff
@ Shawn: Danke. Ich dachte, ich hätte Dateinamen mit verdeckten Leerzeichen, habe aber keine neuen Zeilen in Betracht gezogen: Vorschläge für eine Fehlerbehebung?
Jasonwryan
Fügen Sie -exec echoIhren Suchbefehl hinzu - auf diese Weise wird der Dateiname nicht wiedergegeben, sondern nur eine neue Zeile.
Shawn J. Goff
5

Mit „ohne Rekursion“ meinen Sie, dass Sie, wenn Sie directoryName1Unterverzeichnisse haben, die Dateien in den Unterverzeichnissen nicht zählen möchten? In diesem Fall können Sie alle regulären Dateien in den angegebenen Verzeichnissen zählen:

count=0
for d in directoryName1 directoryName2; do
  for f in "$d"/* "$d"/.[!.]* "$d"/..?*; do
    if [ -f "$f" ]; then count=$((count+1)); fi
  done
done

Beachten Sie, dass der -fTest zwei Funktionen ausführt: Er testet, ob der von einem der obigen Globs gefundene Eintrag eine reguläre Datei ist, und er testet, ob der Eintrag ein Treffer war (wenn einer der Globs mit nichts übereinstimmt, bleibt das Muster wie es ist¹). Wenn Sie alle Einträge in den angegebenen Verzeichnissen unabhängig von ihrem Typ zählen möchten, ersetzen Sie -fdurch -e.

Ksh hat eine Möglichkeit, Muster mit Punktdateien abzugleichen und eine leere Liste zu erstellen, falls keine Datei mit einem Muster übereinstimmt. In ksh können Sie also reguläre Dateien wie folgt zählen:

FIGNORE='.?(.)'
count=0
for x in ~(N)directoryName1/* ~(N)directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

oder alle Dateien einfach so:

FIGNORE='.?(.)'
files=(~(N)directoryName1/* ~(N)directoryName2/*)
count=${#files}

Bash hat verschiedene Möglichkeiten, dies zu vereinfachen. So zählen Sie reguläre Dateien:

shopt -s dotglob nullglob
count=0
for x in directoryName1/* directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

So zählen Sie alle Dateien:

shopt -s dotglob nullglob
files=(directoryName1/* directoryName2/*)
count=${#files}

Wie immer ist es in zsh noch einfacher. So zählen Sie reguläre Dateien:

files=({directoryName1,directoryName2}/*(DN.))
count=$#files

Wechseln Sie (DN.)zu, (DN)um alle Dateien zu zählen.

¹ Beachten Sie, dass jedes Muster mit sich selbst übereinstimmt. Andernfalls sind die Ergebnisse möglicherweise nicht korrekt (wenn Sie z. B. Dateien zählen, die mit einer Ziffer beginnen, können Sie dies nicht einfach tun, for x in [0-9]*; do if [ -f "$x" ]; then …da möglicherweise eine Datei aufgerufen wird [0-9]foo).

Gilles 'SO - hör auf böse zu sein'
quelle
2

Basierend auf einer Zählung Skript , Shawn Antwort und ein Bash Trick , um sicherzustellen , dass auch die Dateinamen mit Zeilenumbrüchen in verwertbarer Form in einer einzigen Zeile gedruckt werden:

for f in *
do
    if [ -d "./$f" ]
    then
        printf %q "$f"
        printf %s ' '
        find "$f" -maxdepth 1 -printf x | wc -c
    fi
done

printf %qGibt eine in Anführungszeichen gesetzte Version einer Zeichenfolge aus, d. h. eine einzeilige Zeichenfolge, die Sie in ein Bash-Skript einfügen können, um sie als Literalzeichenfolge mit (potenziellen) Zeilenumbrüchen und anderen Sonderzeichen zu interpretieren. Siehe zum Beispiel echo -n $'\tfoo\nbar'vs printf %q $'\tfoo\nbar'.

Der findBefehl druckt einfach ein einzelnes Zeichen für jede Datei und zählt diese statt der Zeilen.

l0b0
quelle
1

Hier ist ein „Brute-Force“ -ish Weg , um Ihr Ergebnis zu erhalten, mit find, echo, ls, wc, xargsund awk.

find . -maxdepth 1 -type d -exec sh -c "echo '{}'; ls -1 '{}' | wc -l" \; | xargs -n 2 | awk '{print $1" "$2}'
Die allgemeine
quelle
Diese Arbeit. Aber die Ausgabe ist durcheinander, wenn Sie Verzeichnisse haben, die Leerzeichen im Namen haben.
ShyBoy
Dies meldet falsche Ergebnisse, wenn ein Dateiname ein Zeilenvorschubzeichen enthält.
Shawn J. Goff
-1
for i in *; do echo $i; ls $i | wc -l; done
Dinesh
quelle
4
Willkommen bei U & L. Die Antworten sollten in langer Form mit Erklärungen und nicht einfach mit Code-Drop erfolgen. Bitte erweitern Sie diese und erklären Sie, was los ist. Dies ist auch eine sehr ineffiziente Methode und verarbeitet beispielsweise keine Dateien mit Leerzeichen.
slm