Wie entferne ich alle leeren Verzeichnisse in einem Teilbaum?

151

Wie kann ich alle leeren Verzeichnisse in einem Teilbaum entfernen? Ich habe so etwas benutzt

find . -type d -exec rmdir {} 2>/dev/null \;

Ich muss jedoch mehrere Male ausgeführt werden, um Verzeichnisse zu entfernen, die nur leere Verzeichnisse enthalten. Außerdem ist es ziemlich langsam, besonders unter Cygwin.

maaartinus
quelle
Eine Emacs-Lösung finden Sie auch unter emacs.stackexchange.com/q/12190/2264 .
Sean Allred

Antworten:

222

findDieser Befehl kombiniert GNU- Optionen und Prädikate und sollte folgende Aufgaben erfüllen:

find . -type d -empty -delete
  • -type d beschränkt sich auf Verzeichnisse
  • -empty beschränkt sich auf leere
  • -delete Entfernt jedes Verzeichnis

Der Baum wird von den Blättern entfernt, ohne dass dies angegeben -depthwerden muss -delete.

Christophe Drevet-Droguet
quelle
2
-deleteimpliziert bereits, -depthso dass Sie das nicht manuell angeben müssen.
Jamadagni
1
Danke, das habe ich nicht gemerkt. Antwort aktualisiert.
Christophe Drevet-Droguet
11
Ich würde -mindepth 1hier hinzufügen , um zu verhindern, dass das Startverzeichnis selbst gelöscht wird, wenn es leer wäre.
Greg Dubicki
2
Großartig, funktioniert aber bei meinen alten SunOS-Hosts nicht ...
dokaspar
2
!hat eine besondere Bedeutung für die Schale. Du musst entkommen. Sowas wie: sollte \! -name 'Completed'kurz vorher -deleteklappen. Oder Sie legen einfach eine Markierungsdatei in dieses Verzeichnis.
Christophe Drevet-Droguet
53

Listen Sie die tief verschachtelten Verzeichnisse zuerst auf.

find . -depth -type d -exec rmdir {} \; 2>/dev/null

(Beachten Sie, dass die Umleitung für den findgesamten Befehl gilt , nicht nur für rmdir. Eine Umleitung nur für rmdirwürde zu einer erheblichen Verlangsamung führen, da Sie eine Zwischen-Shell aufrufen müssten.)

Sie können die Ausführung rmdirin nicht leeren Verzeichnissen vermeiden, indem Sie das zu -emptysuchende Prädikat übergeben. GNU find testet das Verzeichnis, wenn es darum geht, den Befehl auszuführen, so dass Verzeichnisse, die gerade geleert wurden, aufgenommen werden.

find . -depth -type d -empty -exec rmdir {} \;

Eine andere Möglichkeit zur Beschleunigung besteht darin, die rmdirAufrufe zu gruppieren . Beide dürften spürbar schneller sein als das Original, insbesondere unter Cygwin. Ich erwarte keinen großen Unterschied zwischen diesen beiden.

find . -depth -type d -print0 | xargs -0 rmdir 2>/dev/null
find . -depth -type d -exec rmdir {} + 2>/dev/null

Welche Methode schneller ist, hängt davon ab, wie viele nicht leere Verzeichnisse Sie haben. Sie können nicht -emptymit Methoden zum Gruppieren von Aufrufen kombinieren , da dann die Verzeichnisse, die nur leere Verzeichnisse enthalten, zum Zeitpunkt der findBetrachtung nicht leer sind.

Eine andere Methode wäre, mehrere Durchläufe durchzuführen. Ob dies schneller ist, hängt unter anderem davon ab, ob die gesamte Verzeichnishierarchie zwischen den findDurchläufen im Festplatten-Cache verbleiben kann .

while [ -n "$(find . -depth -type d -empty -print -exec rmdir {} +)" ]; do :; done

Alternativ können Sie auch zsh verwenden. Das Glob-Qualifikationsmerkmal F stimmt mit nicht leeren Verzeichnissen /^Füberein , entspricht also leeren Verzeichnissen. Verzeichnisse, die nur leere Verzeichnisse enthalten, können nicht so einfach abgeglichen werden.

while rmdir **/*(/N^F); do :; done

(Dies wird beendet, wenn rmdireine leere Befehlszeile empfangen wird.)

Gilles
quelle
Das ist es. Anstelle von 90 Sekunden dauert es 0,90 s.
Maaartinus
@maaartinus: Ich bin gespannt: hast du einen ähnlichen Datensatz, auf den du verzichten könntest -p? Ich hätte nicht gedacht, dass es einen Unterschied machen würde.
Gilles
3
@ Maartinus - andere kleine Optimierungen: Hinzufügen -emptysollte mit diesem funktionieren (obwohl ich nicht genau weiß, wie viel es gewinnen wird). Und sehr, sehr trivial, da Sie wahrscheinlich nicht entfernen möchten ., verwenden -mindepth 1.
Mattdm
Es war nicht das Entfernen, sondern das Starten des Prozesses, was fast die ganze Zeit in Anspruch nahm. Ich hatte das -depthArgument übersehen , was rmdir -pnutzlos macht . Ich habe meinen Kommentar bereits geändert. Die 90er Jahre waren mein ursprünglicher Versuch; es gibt hier nichts überraschendes.
Maaartinus
2
Mir wurde klar, dass wir den rmdirBefehlsaufruf zumindest mit GNU find mit folgendem Befehl entfernen können :find . -depth -type d -empty -delete
Christophe Drevet-Droguet
6

Wenn Sie nur eine -pauf Ihre heften rmdir, wird das in einem Durchgang funktionieren. Es wird nicht schön oder optimal sein, aber es sollte alles bekommen. Dadurch wird rmdir angewiesen, alle nicht leeren übergeordneten Verzeichnisse des zu entfernenden zu entfernen.

Sie können ein wenig sparen, indem Sie den -emptyzu suchenden Test hinzufügen , damit es nicht um nicht leere Verzeichnisse geht.

mattdm
quelle
3

find . -depth -type d -exec rmdir {} +

ist die einfachste und standardkonforme Antwort auf diese Frage.

Die anderen hier gegebenen Antworten hängen leider alle von herstellerspezifischen Verbesserungen ab, die nicht auf allen Systemen vorhanden sind.

schily
quelle
3
Diese Antwort löst für jedes Verzeichnis, das nicht gelöscht werden kann, einen Fehler aus, der möglicherweise nicht wünschenswert ist.
Willem van Ketwich
0

find . -type d -printf "%d %p\n" |\ sort -nr |\ perl -pe 's/^\d+\s//;' |\ while read dir; do \ (rmdir "$dir" > /dev/null 2>&1); \ done

So funktioniert das:

  1. Listen Sie rekursiv alle Verzeichnisse mit ihrer Tiefe auf
  2. Sortieren Sie nach absteigender Reihenfolge ihrer Tiefe
  3. Filtern Sie nur die Verzeichnispfade heraus
  4. Führen Sie rmdirdie Liste einzeln aus
Ashish Ranjan
quelle
0

Ich verwende diese Aliase für häufig verwendete findBefehle, insbesondere wenn ich mit dupeguru den Speicherplatz aufräumt , wobei das Entfernen von Duplikaten zu einer Menge leerer Verzeichnisse führen kann.

Kommentare im Inneren, .bashrcdamit ich sie später nicht vergesse, wenn ich sie anpassen muss.

# find empty directories
alias find-empty='find . -type d -empty'

# fine empty/zero sized files
alias find-zero='find . -type f -empty'

# delete all empty directories!
alias find-empty-delete='find-empty -delete'

# delete empty directories when `-delete` option is not available.
# output null character (instead of newline) as separator. used together
# with `xargs -0`, will handle filenames with spaces and special chars.
alias find-empty-delete2='find-empty -print0 | xargs -0 rmdir -p'

# alternative version using `-exec` with `+`, similar to xargs.
# {}: path of current file
# +: {} is replaced with as many pathnames as possible for each invocation.
alias find-empty-delete3='find-empty -exec rmdir -p {} +'

# for removing zero sized files, we can't de-dupe them automatically
# since they are technically all the same, so they are typically left
# beind. this removes them if needed.
alias  find-zero-delete='find-zero -delete'
alias find-zero-delete2='find-zero -print0 | xargs -0 rm'
alias find-zero-delete3='find-zero -exec rm {} +'
Raychi
quelle
-2

rm -r */Befehl funktionierte leicht für mich. rmsollte erfordern -f, Verzeichnisse mit Dateien mit Gewalt zu entfernen. rm -rsollte nur leere Verzeichnisse entfernen. Ich bin offen, warum das falsch sein könnte. Dies sollte auch Dateien belassen, da */nur Ordner betrachtet werden.

libroman2
quelle
1
Ich würde dringend empfehlen, es zuerst gründlich zu testen, da rmes in erster Linie zum Entfernen von Dateien gedacht ist. Während */nur Verzeichnisse übereinstimmt, habe ich keine Ahnung , was es tiefere Ebene tut auf. Ich kann mir auch vorstellen, dass es nur auf einigen Systemen funktioniert.
Maaartinus