Usuń wszystkie pliki z wyjątkiem określonego podkatalogu z funkcją find

11

Chcę rekurencyjnie usunąć wszystkie pliki, do których nie ma dostępu w danym amomencie w folderze , z wyjątkiem wszystkich plików w podfolderze b.

find a \( -name b -prune \) -o -type f -delete

Jednak pojawia się komunikat o błędzie:

find: Akcja -delete automatycznie włącza -depth, ale -prune nic nie robi, gdy działa -depth. Jeśli mimo to chcesz kontynuować, po prostu jawnie użyj opcji -depth.

Dodanie -depthpowoduje bwłączenie wszystkich plików , co nie może się zdarzyć.

Czy ktoś zna bezpieczny sposób, aby to zadziałało?

naprzód
źródło
@ MichaelKjörling: Spojrzałem na extglob, ale jak uwzględnić wszystko apoza wyjątkiem a/b?
forwardrin
Nie cd a && ls -d !(b/*)zadziała? (Aby to zrobić, rm -rzamiast tego ls -d.)
CVn
Twoja sugestia usuwa podfoldery. Chcę zachować nienaruszone foldery. Chcę znaleźć i usunąć wszystkie pliki w drzewie poniżej a(oprócz plików poniżej a/b).
forwardrin,
Więc po prostu przeskocz -rdo rm. Wygląda na to, że na to, o co pytasz, można dość łatwo odpowiedzieć za pomocą rozszerzonego globowania bash, a następnie to, co zrobisz z wynikiem globowania, zależy od ciebie.
CVn
@ MichaelKjörling Tylko dlatego, że oba problemy mają niejasno podobne rozwiązania, pytania nie są duplikowane. Większość rozwiązań każdego z dwóch problemów nie rozwiązuje drugiego problemu.
Gilles „SO- przestań być zły”

Odpowiedzi:

13

TL; DR: najlepszym sposobem jest użycie -exec rmzamiast -delete.

find a \( -name b -prune \) -o -type f -exec rm {} +

Wyjaśnienie:

Dlaczego znaleźć narzekać podczas próby korzystania -deletez -prune?

Krótka odpowiedź: ponieważ -deleteimplikuje -depthi -depthczyni -prunenieskutecznym.

Zanim przejdziemy do długiej odpowiedzi, najpierw obserwuj zachowanie find zi bez -depth:

$ find foo/
foo/
foo/f1
foo/bar
foo/bar/b2
foo/bar/b1
foo/f2

Nie ma gwarancji na zamówienie w jednym katalogu. Istnieje jednak gwarancja, że ​​katalog zostanie przetworzony przed zawartością. Uwaga foo/przed jakimkolwiek foo/*i foo/barprzed jakimkolwiek foo/bar/*.

Można to odwrócić za pomocą -depth.

$ find foo/ -depth
foo/f2
foo/bar/b2
foo/bar/b1
foo/bar
foo/f1
foo/

Zauważ, że teraz wszystkie foo/*pojawiają się wcześniej foo/. To samo z foo/bar.

Dłuższa odpowiedź:

  • -pruneuniemożliwia zejście find do katalogu. Innymi słowy, -prunepomija zawartość katalogu. W twoim przypadku -name b -pruneuniemożliwia znalezienie zejście do dowolnego katalogu o nazwie b.
  • -depthwyszukuje, aby przetworzyć zawartość katalogu przed samym katalogiem. Oznacza to, że do czasu, aż find przetworzy pozycję katalogu, bjej zawartość została już przetworzona. W związku z tym -prunejest nieskuteczny -depth.
  • -deleteoznacza -depth, że może najpierw usunąć pliki, a następnie pusty katalog. -deleteodmawia usunięcia niepustych katalogów. Myślę, że byłoby możliwe dodanie opcji wymuszenia -deleteusunięcia niepustych katalogów i / lub zapobieżenia -deletesugerowaniu -depth. Ale to inna historia.

Istnieje inny sposób na osiągnięcie tego, co chcesz:

find a -not -path "*/b*" -type f -delete

To może być łatwiejsze do zapamiętania.

To polecenie wciąż schodzi do katalogu bi przetwarza każdy pojedynczy plik tylko po to, -notaby je odrzucić. Może to być problem z wydajnością, jeśli katalog bjest ogromny.

-pathdziała inaczej niż -name. -namepasuje tylko do nazwy (pliku lub katalogu), podczas gdy -pathpasuje do całej ścieżki. Na przykład obserwuj ścieżkę /home/lesmana/foo/bar. -name -barbędzie pasować, ponieważ nazwa to bar. -path "*/foo*"będzie pasować, ponieważ ciąg /fooznajduje się na ścieżce. -pathma pewne zawiłości, które powinieneś zrozumieć przed użyciem. Przeczytaj stronę podręcznika, findaby uzyskać więcej informacji.

Uważaj, że nie jest to w 100% niezawodne. Istnieją szanse na „fałszywe alarmy”. Sposób, w jaki powyższe polecenie jest napisane, pomija każdy plik, który ma dowolny katalog nadrzędny, którego nazwa zaczyna się od b(dodatnia). Ale pominie także każdy plik, którego nazwa zaczyna się bniezależnie od pozycji w drzewie (fałszywie dodatni). Można to naprawić, pisząc lepsze wyrażenie niż "*/b*". Zostało to ćwiczeniem dla czytelnika.

Zakładam, że użyłeś ai bjako symbole zastępcze, a prawdziwe nazwiska są bardziej podobne do allosaurusi brachiosaurus. Jeśli wstawisz brachiosaurusw miejsce bwówczas ilość fałszywych alarmów zostanie drastycznie zmniejszona.

Przynajmniej fałszywe pozytywy będzie nie usunięte, więc będzie nie być tak tragiczne. Co więcej, możesz sprawdzić fałszywe alarmy, uruchamiając najpierw komendę bez -delete(ale pamiętaj, aby umieścić domyślną -depth) i sprawdź dane wyjściowe.

find a -not -path "*/b*" -type f -depth
lesmana
źródło
-not -pathbyło po prostu rzeczą! Dzięki za hojne wyjaśnienie!
forwardrin,
1
Przydałoby się trochę wyjaśnienia, dlaczego -not -pathdziała, podczas gdy -prunenie. Dlaczego może -not -pathwspółistnieć z -depth?
Faheem Mitha
3

Po prostu użyj rmzamiast -delete:

find a -name b -prune -o -type f -exec rm -f {} +
Stéphane Chazelas
źródło
1
Czy możesz wyjaśnić, dlaczego rmdziała, a dlaczego deletenie?
Faheem Mitha
1
Och, chyba dlatego, że „-delete odmawia usunięcia niepustych katalogów.”, Cytując @lesmana. Więc odmawia usunięcia niepustych katalogów. Ale rmnie ma tego problemu. Ale niezależnie od tego, opracowanie byłoby dobre.
Faheem Mitha
@FaheemMitha, odpowiedź na to pytanie. -deleteimplikuje -depth, co oczywiście nie działa -prune. -pathdziała, ale nie przestaje findschodzić do katalogów, których nie musi eksplorować.
Stéphane Chazelas
0

Powyższe odpowiedzi i wyjaśnienia były bardzo pomocne.

Używam obejść „-exec rm {} +” lub „-not -path ... -delete”, ale mogą one być znacznie wolniejsze niż „find ... -delete”. Widziałem „find ... -delete ”działa 5 razy szybciej niż„ -exec rm {} + ”w głębokich katalogach w systemie plików NFS.

Rozwiązanie „-not path” ma oczywisty narzut związany z przeglądaniem wszystkich plików w wykluczonych katalogach i poniżej.

„Find .. -exec rm {} +” wywołuje rm, który wykonuje wywołania systemowe:

fstatat(AT_FDCWD, path...); 
unlinkat(AT_FDCWD, path, 0)

Funkcja „find -delete” wykonuje wywołania systemowe:

 fd=open(dir,...);
 fchdir(fd); 
 fstatat(AT_FDCWD, filename,...)
 unlinkat(dirfd, filename,...)

Zatem polecenie „-exec rm {} +” rm wykonuje pełną ścieżkę do wyszukiwania i-węzłów dwa razy dla każdego pliku, ale polecenie „find -delete” wykonuje stat i rozłącza nazwę pliku w bieżącym katalogu. To duża wygrana, gdy usuwasz wiele plików w jednym katalogu.

(tryb kwilenia włączony (przepraszam))

Wygląda na to, że projekt interakcji między -depth, -delete i -prune niepotrzebnie eliminuje najbardziej efektywny sposób wykonywania wspólnego działania „usuń pliki z wyjątkiem tych katalogów w -prune”

Kombinacja „-type f -delete” powinna być w stanie działać bez -depth, ponieważ nie próbuje usuwać katalogów. Alternatywnie, jeśli polecenie „znajdź” zawierało akcję „-deletefile”, która mówi „nie usuwaj katalogów”, -depth nie musiałby być implikowany.

Polecenia xargs lub find -exec do rm mogłyby zostać przyspieszone, jeśli rm miałby opcję sortowania nazw plików, otwierania katalogów i wykonywania unlinkat (dir_fd, nazwa pliku) zamiast rozłączania pełnych ścieżek. Już robi unlinkat (dir_fd, nazwa_pliku) podczas rekurencji przez katalogi z opcją -r.

(wyłączony tryb kwilenia)

Donald Mears
źródło