dla pętli w folderach ze znakiem \ n w nazwach

9

Mam kilka folderów z \ncharakterem to ich nazwy.

na przykład:

$ ls
''$'\n''Test'

Dotyczy to folderu z nazwą testu i pustą linią przed jego nazwą.

Kiedy więc uruchomię kilka takich skryptów w katalogu nadrzędnym:

while IFS= read -r d; do 
    rmdir $d
done < <(find * -type d)

To pokazuje:

rmdir: failed to remove '': No such file or directory
rmdir: failed to remove 'Test': No such file or directory

Ponieważ działa dwa razy, raz włączony, \na drugi włączony Test, ponieważ nazwa folderu ma dwie linie.

Jak więc rozwiązać ten problem, aby skrypt wiedział, że \nTestjest tylko jeden folder?

Tara S Volpe
źródło
3
Musisz użyć -print0dyrektywy find i -dopcji read. Zobacz stackoverflow.com/a/40189667/7552
glenn jackman
1
@glennjackman Proszę odpowiedzieć!
deser
@glennjackman Dzięki za odpowiedź, ale find * -type d -print0 | while IFS= read -d '' file ; do rmdir $file ; donepolecenie ma takie wyjście rmdir: failed to remove 'Test': No such file or directory.
Tara S Volpe
5
Państwo musi podać zmienną:rmdir "$file"
Glenn Jackman
1
@glennjackman Wystarczy opublikować jako odpowiedź. To właściwe rozwiązanie, a poza tym komentarze nie są najlepszym miejscem na to. Głosowanie już sugerowane :)
Sergiy Kolodyazhnyy

Odpowiedzi:

12

Masz tam tylko jedno polecenie, więc wystarczy zadzwonić findz -execflagą rmdir:

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

Lub użyj -deleteopcji jak w find -type d -delete, ale nie będzie działać z niepustymi katalogami. Do tego będziesz potrzebować również -emptyflagi. Uwaga: -deletesugeruje -depth, że można to pominąć. Zatem kolejna realna alternatywa, która utrzymuje wszystko jako jeden proces:

find -type d -empty -delete

Jeśli katalog nie jest pusty, użyj rm -rf {} \;. Aby wyizolować tylko katalogi z \nnazwą pliku, możemy połączyć cytowanie ANSI-C basha $'...'z -nameopozycją:

find  -type d -name $'*\n*' -empty -delete

POSIX-ly mogliśmy sobie z tym poradzić w następujący sposób:

find -depth  -type d -name "$(printf '*\n*' )" -exec rmdir {} \;

Warto wspomnieć, że jeśli twoim celem jest usunięcie katalogów, -deleteto wystarczy, jednak jeśli chcesz wykonać polecenie w katalogu, -execjest to najbardziej odpowiednie.

Zobacz też

Sergiy Kolodyazhnyy
źródło
2
… Używaj rm -rf ostrożnie . ; P Nie findma -deleteopcji btw? Usuwa puste katalogi i generuje błędy dla niepustych katalogów typu rmdir- może być tańszy.
deser
3
@dessert Robi to i dodałem to, ale -deletenie usuwa niepustych katalogów. Dlatego ostrożne użycierm -rf
Sergiy Kolodyazhnyy
6
Zawsze uruchamiaj na sucho takie findpolecenia bez żadnego niszczącego ładunku (tj. Usuń -deletelub -exec whatever \;), aby sprawdzić, czy lista dotkniętych plików jest rzeczywiście poprawna i nie zawiera elementów, które nie powinny zostać usunięte ...
Byte Commander
2
To jest askubuntu.com, abyśmy mogli założyć GNU find. Służy -exec rmdir {} +do grupowania wielu argumentów w jednym rmdirwierszu poleceń. I BTW „ ale to nie będzie działać z niepustymi katalogami. Dotyczy to rmdirrównież, więc to właściwie nie jest różnica -delete. To różnica od rm -r.
Peter Cordes,
2
find -type d -empty -delete( -deleteimplikuje -depth).
Kevin
10

Możesz użyć globusów powłoki zamiast find:

for d in */ ; do 
    rmdir "$d"
done

Glob powłoki */dopasowuje wszystkie foldery w bieżącym katalogu. Ta konstrukcja pętli zapewnia automatyczne dzielenie wyrazów automatycznie.

Zauważ, że w zależności od opcji powłoki może to zignorować ukryte foldery (nazwa zaczyna się od a.). To zachowanie można zmienić, aby dopasować wszystkie pliki dla bieżącej sesji za pomocą polecenia shopt -s dotglob.

Nie zapomnij też zawsze podawać swoich zmiennych.

Bajt Dowódca
źródło
glob znajdzie tylko pliki / katalogi w bieżącym katalogu, a nie rekurencyjnie, jak to findrobi
ilkkachu
1
Masz rację @ilkkachu. Można to zmienić, włączając opcję powłoki globstar shopt -s globstari używając **/*/zamiast niej globu.
Bajt Dowódca
6

Obie dotychczas napisane odpowiedzi wywołują rmdirraz na katalog, ale jak rmdirmożna wziąć wiele argumentów, zastanawiam się: czy nie ma bardziej wydajnego sposobu?

Można po prostu zrobić

rmdir */

i jest to zdecydowanie najłatwiejszy i najbardziej wydajny sposób, ale może generować błąd w przypadku wielu katalogów (zobacz Jaka jest maksymalna długość argumentów wiersza poleceń w gnome-terminal? ). Jeśli chcesz, aby to podejście działało rekurencyjnie, włącz globstaropcję powłoki za pomocą shopt -s globstari użyj **/*/zamiast */.

Z GNU find(i jeśli nie chcemy tylko używać -delete), możemy to zrobić

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

który xargsbuduje linię poleceń „w taki sam sposób, jak buduje jej linie poleceń” ( man find). Zastępstwo -depthdla -maxdepth 1jeśli nie chcesz go do pracy rekurencyjnie.

Trzeci i genialny sposób IMO wyjaśnia steeldriver w tej odpowiedzi :

printf '%s\0' */ | xargs -0 rmdir

Wykorzystuje to wbudowaną powłokę printfdo zbudowania listy argumentów rozdzielanej zerami, ta lista jest następnie przesyłana potokowo, do xargsktórej wywołań rmdirdokładnie tak często, jak to konieczne. Można zrobić to praca z rekurencyjnie shopt -s globstari **/*/zamiast */jak wyżej.

deser
źródło
2
findjest rekurencyjny, ale pętla PO nie jest. Aby zreplikować to zachowanie, użyj find -maxdepth 1 -type d -exec rmdir {} +. ( -depthw tym przypadku jest zbędny.) Świetna sztuczka z printfnaśladowaniem find -print0, nie widziałem tego wcześniej.
Peter Cordes,
1
O! I widział lsi whilepętla, brakowało że ich przekierowanie z podstawienia technologicznego findzamiast przewodów rurowych lsdo pętli i po prostu nie pokazano go z jakiegoś powodu. To find *jest naprawdę pochowane. Tak, jest rekurencyjny i zakończy się niepowodzeniem, jeśli w katalogu jest zbyt wiele pozycji katalogu, w tym katalogów innych niż katalogi, ponieważ nie używa */. find .byłoby znacznie lepiej, ponieważ findjest szybsze statniż globalna ekspansja bash.
Peter Cordes