Jak usunąć wszystkie puste katalogi w poddrzewie?

151

Jak mogę usunąć wszystkie puste katalogi w poddrzewie? Użyłem czegoś takiego

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

ale muszę uruchamiać wiele razy, aby usunąć katalogi zawierające tylko puste katalogi. Co więcej, jest dość powolny, szczególnie pod cygwinem.

maaartinus
źródło
Zobacz także emacs.stackexchange.com/q/12190/2264, aby znaleźć rozwiązanie emacs.
Sean Allred

Odpowiedzi:

221

Łącząc findopcje GNU i predykaty, to polecenie powinno wykonać zadanie:

find . -type d -empty -delete
  • -type d ogranicza się do katalogów
  • -empty ogranicza się do pustych
  • -delete usuwa każdy katalog

Drzewo jest usuwane z liści bez potrzeby określania, -depthjak sugeruje to -delete.

Christophe Drevet-Droguet
źródło
2
-deletejuż to sugeruje, -depthwięc nie trzeba tego określać ręcznie.
jamadagni
1
Dzięki, nie zdawałem sobie z tego sprawy. Odpowiedź zaktualizowana.
Christophe Drevet-Droguet
11
Dodałbym -mindepth 1tutaj, aby zapobiec usunięciu samego katalogu startowego, jeśli byłby pusty.
Greg Dubicki,
2
Świetnie, ale nie działa na moich starych hostach SunOS ...
dokaspar
2
!ma specjalne znaczenie dla powłoki. Musisz uciec. Coś w stylu: \! -name 'Completed'tuż przed -deletepowinno działać. Lub po prostu umieść plik znaczników w tym katalogu.
Christophe Drevet-Droguet
53

Wymień katalogi głęboko zagnieżdżone jako pierwsze.

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

(Zauważ, że przekierowanie dotyczy całego findpolecenia, a nie tylko rmdir. Przekierowanie tylko dla rmdirspowoduje znaczne spowolnienie, ponieważ będziesz musiał wywołać powłokę pośrednią).

Możesz uniknąć uruchamiania rmdirniepustych katalogów, przekazując -emptypredykat do znalezienia. GNU find sprawdza katalog, gdy ma zamiar uruchomić polecenie, więc katalogi, które właśnie zostały opróżnione, zostaną pobrane.

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

Innym sposobem na przyspieszenie byłoby grupowanie rmdirwywołań. Oba będą prawdopodobnie zauważalnie szybsze niż oryginał, szczególnie pod Cygwinem. Nie oczekuję dużej różnicy między tymi dwoma.

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

Która metoda jest szybsza, zależy od liczby niepustych katalogów. Nie można łączyć -emptyz metodami grupowania wywołań, ponieważ wtedy katalogi zawierające tylko puste katalogi nie są puste, dopóki się na nie nie findspojrzy.

Inną metodą byłoby uruchomienie wielu przebiegów. To, czy jest to szybsze, zależy od wielu rzeczy, w tym od tego, czy cała hierarchia katalogów może pozostać w pamięci podręcznej dysku między finduruchomieniami.

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

Alternatywnie użyj zsh. Glob kwalifikator F mecze niepuste katalogi, więc /^Fpasuje do pustych katalogów. Katalogów zawierających tylko puste katalogi nie można tak łatwo dopasować.

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

(Kończy się, gdy rmdirotrzymuje pusty wiersz poleceń).

Gilles
źródło
Otóż ​​to. Zamiast 90 sekund zajmuje 0,90 s.
maaartinus
@maaartinus: Jestem ciekawy: czy masz podobny zestaw danych, w którym możesz spróbować bez -p? Nie pomyślałbym, że to coś zmieni.
Gilles
3
@maartinus - inne małe optymalizacje: dodawanie -emptypowinno działać z tym (chociaż nie jestem pewien, ile dokładnie zyska). I bardzo, bardzo trywialnie, ponieważ prawdopodobnie nie chcesz go usuwać ., używaj -mindepth 1.
mattdm
Nie było to usunięcie, ale proces uruchamiania się narzut, co zajęło prawie cały czas. Przeoczyłem -depthargument, który czyni rmdir -pbezużytecznym. Zmieniłem już swój komentarz. Lata 90. były moją pierwotną próbą; nie ma w tym nic zaskakującego.
maaartinus
2
Zdałem sobie sprawę, że możemy rmdircałkowicie usunąć polecenie, przynajmniej za pomocą GNU find, za pomocą tego polecenia:find . -depth -type d -empty -delete
Christophe Drevet-Droguet
6

Jeśli po prostu przyłożysz -pswoją kartę rmdir, zadziała to w jednym przejściu. Nie będzie ładna ani optymalna, ale powinna mieć wszystko. To każe rmdirowi usunąć wszelkie niepuste katalogi nadrzędne tego, który usuwasz.

Możesz trochę zaoszczędzić, dodając -emptytest do znalezienia, aby nie zawracał sobie głowy niepustymi katalogami.

mattdm
źródło
3

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

jest najprostszą i standardową odpowiedzią na to pytanie.

Inne podane tutaj odpowiedzi niestety zależą od ulepszeń specyficznych dla dostawcy, które nie istnieją we wszystkich systemach.

schily
źródło
3
Ta odpowiedź powoduje błąd dla każdego katalogu, którego nie można usunąć, co może być mniej niż pożądane.
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

Oto jak to działa:

  1. Rekurencyjnie wypisz wszystkie katalogi wraz z ich głębokością
  2. Sortuj według malejącej kolejności ich głębokości
  3. Odfiltruj tylko ścieżki katalogu
  4. Uruchom rmdirlistę jeden po drugim
Ashish Ranjan
źródło
0

Używam tych aliasów do często używanych findpoleceń, szczególnie podczas czyszczenia miejsca na dysku za pomocą dupeguru , gdzie usunięcie duplikatów może spowodować powstanie wielu pustych katalogów.

Komentarze w środku, .bashrcwięc nie zapomnę ich później, kiedy będę musiał je poprawić.

# 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
źródło
-2

rm -r */polecenie działało dla mnie łatwo. rmpowinien wymagać -fwymuszonego usunięcia katalogów z plikami. rm -rpowinien usuwać tylko puste katalogi. Jestem otwarty na to, dlaczego to może być źle. Powinno to również pozostawić pliki, ponieważ przegląda */tylko foldery.

libroman2
źródło
1
Zdecydowanie polecam najpierw przetestować go dokładnie, ponieważ rmma on przede wszystkim na celu usunięcie plików. Chociaż */pasuje tylko do katalogów, nie mam pojęcia, co robi na głębszych poziomach. Mogę sobie również wyobrazić, że działa tylko na niektórych systemach.
maaartinus