Czy ograniczyć POSIX do określonej głębokości?

15

Zauważyłem niedawno, że specyfikacje POSIXfind nie obejmują -maxdepthpodstawowego.

Dla tych, którzy go nie znają, głównym celem -maxdepthjest ograniczenie o ile poziomów findzejdzie. -maxdepth 0powoduje przetwarzanie tylko argumentów wiersza poleceń; -maxdepth 1obsługuje tylko wyniki bezpośrednio w argumentach wiersza poleceń itp.

Jak mogę uzyskać zachowanie równoważne z podstawowym systemem innym niż POSIX, -maxdepthużywając tylko opcji i narzędzi określonych przez POSIX?

(Uwaga: Oczywiście mogę uzyskać ekwiwalent -maxdepth 0po prostu -prunejako pierwszy operand, ale nie obejmuje to innych głębokości).

Dzika karta
źródło
@StevenPenny, FreeBSD -depth -2, -depth 1... podejście może być postrzegane jako lepsze niż GNU -maxdepth/-mindepth
Stéphane Chazelas
@ StéphaneChazelas w obu kierunkach - POSIX find powinien mieć jedno lub drugie; inaczej jest okaleczony
Steven Penny,
1
Przynajmniej dla -maxdepth/ -mindepthistnieją rozsądne alternatywy (zauważ, że -pathjest to najnowszy dodatek do POSIX). Alternatywy dla -timexylub -mtime -3m(lub -mmin -3) są o wiele bardziej kłopotliwe. Niektórzy lubią -execdir/ -deletenie mają niezawodnej alternatywy.
Stéphane Chazelas,
2
@StevenPenny, zaloguj się na austingroupbugs.net, aby poprosić o dodanie. Widziałem, jak dodawano rzeczy bez potrzeby sponsora, gdy istniało silne uzasadnienie. Prawdopodobnie lepszym działaniem byłoby dodanie tak wielu implementacji, aby najpierw je dodało, więc POSIX musiałby po prostu określić istniejące, co jest ogólnie mniej sporne.
Stéphane Chazelas,
@ StéphaneChazelas w moim przypadku skończyło się na nazywaniu plików bezpośrednio, ale dziękuję; Mogę złożyć bilet, jeśli to się powtórzy
Steven Penny,

Odpowiedzi:

7

Możesz użyć, -pathaby dopasować daną głębokość i przycinać tam. Na przykład

find . -path '*/*/*' -prune -o -type d -print

byłoby maxdepth 1, ponieważ *pasuje do ., */*dopasowuje ./dir1i */*/*dopasowuje, ./dir1/dir2które są przycinane. Jeśli używasz bezwzględnego katalogu początkowego, musisz również dodać do niego wiodącą /pozycję -path.

meuh
źródło
Hmmm, trudne. Czy nie możesz po prostu usunąć jednej warstwy /*z końca wzoru, wyjąć -ooperatora i uzyskać ten sam wynik?
Wildcard
Nie, ponieważ *pasuje /również, więc reż a/b/c/d/epasowałby -path */*, niestety.
Meuh
Ale a/b/c/d/enigdy nie zostałby osiągnięty , ponieważ -prunezostałby zastosowany do a/b...
Wildcard
1
Przepraszam, źle to odczytałem -prunei -ozostały usunięte. Jeśli utrzymasz -pruneproblem, problem polega na tym, że */*nic nie będzie pasowało na poziomie wyższym niż maxdepth, np. Pojedynczy katalog a.
Meuh
11

Podejście @ meuh jest nieefektywne jak jego -maxdepth 1 podejście pozwala nadal findczytać zawartość katalogów na poziomie 1, aby później je zignorować. Nie będzie również działał poprawnie z niektórymi findimplementacjami (w tym GNU find), jeśli niektóre nazwy katalogów zawierają sekwencje bajtów, które nie tworzą prawidłowych znaków w ustawieniach regionalnych użytkownika (np. Dla nazw plików z innym kodowaniem znaków).

find . \( -name . -o -prune \) -extra-conditions-and-actions

jest bardziej kanonicznym sposobem implementacji GNU -maxdepth 1(lub FreeBSD -depth -2).

Ogólnie rzecz biorąc, to -depth 1chcesz (-mindepth 1 -maxdepth 1 ), ponieważ nie chcesz brać pod uwagę .(głębokość 0), a następnie jest jeszcze prostsze:

find . ! -name . -prune -extra-conditions-and-actions

Dla -maxdepth 2 to staje się:

find . \( ! -path './*/*' -o -prune \) -extra-conditions-and-actions

I tu właśnie występują problemy z nieprawidłowymi postaciami.

Na przykład, jeśli masz katalog o nazwie, Stéphanektóry éjest zakodowany w zestawie znaków iso8859-1 (aka latin1) (bajt 0xe9), jak to było najczęściej w Europie Zachodniej i Ameryce do połowy 2000 roku, to bajt 0xe9 nie jest poprawny znak w UTF-8. Zatem w ustawieniach regionalnych UTF-8 *symbol wieloznaczny (z niektórymi findimplementacjami) nie będzie pasował, Stéphaneponieważ *ma 0 lub więcej znaków, a 0xe9 nie jest znakiem.

$ locale charmap
UTF-8
$ find . -maxdepth 2
.
./St?phane
./St?phane/Chazelas
./Stéphane
./Stéphane/Chazelas
./John
./John/Smith
$ find . \( ! -path './*/*' -o -prune \)
.
./St?phane
./St?phane/Chazelas
./St?phane/Chazelas/age
./St?phane/Chazelas/gender
./St?phane/Chazelas/address
./Stéphane
./Stéphane/Chazelas
./John
./John/Smith

Mój find (gdy dane wyjściowe trafiają do terminala) wyświetla ten nieprawidłowy bajt 0xe9 jak ?wyżej. Widać, że St<0xe9>phane/Chazelasto nie było pruned.

Możesz obejść ten problem, wykonując:

LC_ALL=C find . \( ! -path './*/*' -o -prune \) -extra-conditions-and-actions

Należy jednak pamiętać, że wpływa to na wszystkie ustawienia regionalne findi każdą uruchomioną aplikację (np. Za pośrednictwem-exec predykatów).

$ LC_ALL=C find . \( ! -path './*/*' -o -prune \)
.
./St?phane
./St?phane/Chazelas
./St??phane
./St??phane/Chazelas
./John
./John/Smith

Teraz naprawdę dostaję -maxdepth 2 ale é w drugim Stéphane poprawnie zakodowanym w UTF-8 jest wyświetlane jako ??bajty 0xc3 0xa9 (uważane za dwa pojedyncze niezdefiniowane znaki w ustawieniach regionalnych C) kodowania é UTF-8 niedrukowalne znaki w ustawieniach regionalnych C.

A gdybym dodał a -name '????????', otrzymałbym niewłaściwą Stéphane (kodowaną w iso8859-1).

Aby zastosować zamiast dowolnych ścieżek ., wykonaj następujące czynności:

find some/dir/. ! -name . -prune ...

dla -mindepth 1 -maxdepth 1lub:

find some/dir/. \( ! -path '*/./*/*' -o -prune \) ...

dla -maxdepth 2 .

Nadal zrobiłbym:

(cd -P -- "$dir" && find . ...)

Po pierwsze, ponieważ powoduje to, że ścieżki są krótsze, co zmniejsza prawdopodobieństwo zbyt długiego napotkania ścieżki lub listy argumentów zbyt długich , ale także obejścia faktu, że findnie może obsługiwać argumentów arbitralnej ścieżki (z wyjątkiem -fFreeBSD find), ponieważ będzie się dusić wartości $dirjak !lub -print...


W -opołączeniu z negacją jest powszechną sztuczką do uruchamiania dwóch niezależnych zestawów -condition/ -actionin find.

Jeśli chcesz uruchomić -action1na spotkaniu plików -condition1i niezależnie -action2na spotkaniu plików -condition2, nie możesz:

find . -condition1 -action1 -condition2 -action2

Podobnie jak w -action2przypadku plików, które się spełniają oba warunki.

Ani:

find . -contition1 -action1 -o -condition2 -action2

Jak -action2nie będzie działać na plikach, które spełniają oba warunki.

find . \( ! -condition1 -o -action1 \) -condition2 -action2

działa tak, \( ! -condition1 -o -action1 \)jakby rozwiązał prawdę dla każdego pliku. Zakłada się, że -action1to działanie (jak -prune, -exec ... {} +), które zawsze zwraca wartość true . Dla takich działań -exec ... \;może być zwracana wartość false , możesz dodać inną, -o -somethinggdzie -somethingjest nieszkodliwa, ale zwraca wartość true, tak jak -truew GNU findlub -links +0lub -name '*'(choć zwróć uwagę na problem dotyczący nieprawidłowych znaków powyżej).

Stéphane Chazelas
źródło
1
Pewnego dnia napotkam kilka chińskich plików i będę bardzo zadowolony, że przeczytałem wiele odpowiedzi na temat ustawień regionalnych i prawidłowych znaków. :)
Wildcard
2
@Wildcard, ty (a nawet bardziej Chińczyk) masz większe problemy z brytyjskimi, francuskimi ... nazwami plików niż chińskimi, ponieważ chińskie nazwy plików są częściej kodowane w UTF-8 niż nazwy plików skryptów alfabetycznych które na ogół można objąć zestawem znaków jednobajtowych, który do niedawna był normą. Istnieją inne wielobajtowe zestawy znaków, które obejmują chińskie znaki, ale spodziewałbym się, że Chińczycy przeszliby na UTF-8 wcześniej niż ludzie z Zachodu, ponieważ mają one wiele nieprzyjemnych problemów. Zobacz także przykład edycji.
Stéphane Chazelas,
0

Natknąłem się na problem, w którym potrzebowałem sposobu ograniczenia głębokości podczas wyszukiwania wielu ścieżek (zamiast tylko .).

Na przykład:

$ find dir1 dir2 -name myfile -maxdepth 1

Doprowadziło mnie to do alternatywnego podejścia z użyciem -regex. Istotą jest:

-regex '(<list of paths | delimited>)/<filename>'

Tak więc powyższe byłoby:

$ find dir1 dir2 -name myfile -regextype awk -regex '(dir1|dir2)/myfile' # GNU
$ find -E dir1 dir2 -name myfile -regex '(dir1|dir2)/myfile' # MacOS BSD

Bez nazwy pliku:

$ find dir1 dir2 -name myfile -maxdepth 1 # GNU

-regex '(<list of paths | delimited>)/<anything that's not a slash>$'

$ find dir1 dir2 -name myfile -regextype awk -regex '(dir1|dir2)/[^/]*$' # GNU
$ find -E dir1 dir2 -name myfile -regex '(dir1|dir2)/[^/]*$' # MacOS BSD

Wreszcie -maxdepth 2zmiany wyrażeń regularnych:'(dir1|dir2)/([^/]*/){0,1}[^/]*$'

Alissa H.
źródło
1
To pytanie wymaga jednak standardowego rozwiązania (jak w POSIX). Działa również -maxdepthz wieloma ścieżkami wyszukiwania.
Kusalananda