Jak używać opcji „find” w „sh” w sh?

219

Nie do końca rozumiem przykład podany na podstawie man find: czy ktoś może podać mi kilka przykładów i wyjaśnień? Czy mogę połączyć w nim wyrażenie regularne?


Bardziej szczegółowe pytanie jest następujące:

Napisz skrypt powłoki changeall, który ma podobny interfejs changeall [-r|-R] "string1" "string2". Będzie znaleźć wszystkie pliki z przyrostkiem .h, .C, .cc, lub .cppi zmienić wszystkie wystąpienia string1do string2. -rjest opcją pozostania tylko w aktualnym katalogu lub w tym katalogu.

UWAGA:

  1. W przypadku nierekurencyjnym, lsNIE jest dozwolone, mogliśmy tylko użyć findi sed.
  2. Próbowałem, find -depthale NIE było obsługiwane. Właśnie dlatego zastanawiałem się, czy mogę -prunepomóc, ale nie zrozumiałem przykładu man find.

EDYCJA 2: Wykonałem zadanie, nie zadawałem szczegółowych pytań, ponieważ chciałbym to zakończyć sam. Ponieważ już to zrobiłem i przekazałem, teraz mogę zadać całe pytanie. Udało mi się też zakończyć zadanie bez użycia -prune, ale i tak chciałbym się tego nauczyć.

derrdji
źródło

Odpowiedzi:

438

Mylę -prunesię, że jest to działanie (jak -print), a nie test (jak -name). Zmienia listę „czynności do wykonania”, ale zawsze zwraca wartość true .

Ogólny wzór użycia -prunejest następujący:

find [path] [conditions to prune] -prune -o \
            [your usual conditions] [actions to perform]

Prawie zawsze chcesz -o(logiczne OR) natychmiast po tym -prune, ponieważ ta pierwsza część testu (do włącznie -prune) zwróci false dla rzeczy, których naprawdę chcesz (tj. Rzeczy, których nie chcesz przycinać).

Oto przykład:

find . -name .snapshot -prune -o -name '*.foo' -print

Znajduje to pliki „* .foo”, które nie znajdują się w katalogach „.snapshot”. W tym przykładzie -name .snapshotskłada się [conditions to prune], i -name '*.foo' -printjest [your usual conditions]i [actions to perform].

Ważne uwagi :

  1. Jeśli wszystko, co chcesz zrobić, to wydrukować wyniki, które możesz wykorzystać do pominięcia -printakcji. Zasadniczo nie chcesz tego robić podczas używania -prune.

    Domyślne zachowanie find to „i” całe wyrażenie wraz z -printakcją, jeśli -prunena końcu nie ma żadnych akcji innych niż (ironicznie). Oznacza to, że napisanie tego:

    find . -name .snapshot -prune -o -name '*.foo'              # DON'T DO THIS

    jest równoznaczne z napisaniem tego:

    find . \( -name .snapshot -prune -o -name '*.foo' \) -print # DON'T DO THIS

    co oznacza, że ​​wypisze również nazwę katalogu, który przycinasz, co zwykle nie jest tym, czego chcesz. Zamiast tego lepiej jest wyraźnie określić -printakcję, jeśli tego właśnie chcesz:

    find . -name .snapshot -prune -o -name '*.foo' -print       # DO THIS
  2. Jeśli zdarzy się, że „zwykły warunek” pasuje do plików, które również pasują do stanu suszonych śliwek, pliki te nie zostaną uwzględnione w wynikach. Aby to naprawić, dodaj -type dpredykat do stanu śliwek.

    Załóżmy na przykład, że chcemy wyciąć każdy katalog, który zaczynał się .git(co jest wprawdzie nieco wymyślone - zwykle wystarczy usunąć dokładnie taką nazwę .git), ale poza tym chciałem zobaczyć wszystkie pliki, w tym pliki podobne .gitignore. Możesz spróbować tego:

    find . -name '.git*' -prune -o -type f -print               # DON'T DO THIS

    Nie byłoby to uwzględnione .gitignorew danych wyjściowych. Oto poprawiona wersja:

    find . -name '.git*' -type d -prune -o -type f -print       # DO THIS

Dodatkowa wskazówka: jeśli używasz wersji GNU find, strona texinfo dla findzawiera bardziej szczegółowe wyjaśnienie niż strona podręcznika (tak jak w przypadku większości narzędzi GNU).

Laurence Gonsalves
źródło
6
nie jest to w 100% oczywiste w tekście (ale ponieważ drukujesz tylko „* .foo”, nie powoduje to konfliktu), ale część -prune również nie drukuje niczego (nie tylko katalogów) o nazwie „.snapshot”. tzn. -prunenie działa tylko na katalogach (ale w przypadku katalogów zapobiega także wprowadzaniu katalogów spełniających ten warunek, tj. tutaj katalogów spełniających ten warunek -name .snapshot).
Olivier Dulac
12
i +1 dla ciebie za dobrze wykonane wyjaśnienie (a zwłaszcza ważną uwagę). Powinieneś to przesłać programistom wyszukiwania (ponieważ strona podręcznika man nie wyjaśnia „przycinania” zwykłych ludzi ^^ Wiele prób to rozgryzłem, a ja nie widziałem efektu ubocznego, o którym nas ostrzegasz)
Olivier Dulac
2
@OlivierDulac To bardzo dobra uwaga na temat potencjalnego usuwania plików, które chcesz zachować. Zaktualizowałem odpowiedź, aby to wyjaśnić. tak -prunena marginesie, to nie jest tak naprawdę przyczyną. Problem polega na tym, że „zwarcia” operatora lub mają niższy priorytet niż i. Rezultat końcowy jest taki, że jeśli .snapshotnapotkany plik zostanie napotkany, będzie pasował do pierwszego -name, -prunea następnie nie zrobi nic (tylko zwróci true), a następnie znak „ or” zwróci true, ponieważ jego lewy argument był prawdziwy. Akcja (np . -print:) jest częścią drugiego argumentu, więc nigdy nie ma szansy na wykonanie.
Laurence Gonsalves,
3
+1 w końcu dowiedziałem się, dlaczego potrzebuję -printna końcu, mogę teraz przestać dodawać \! -path <pattern>oprócz-prune
Nędznej Zmiennej
6
Zauważ, że „-o” jest skrótem dla „-or”, który (choć nie jest zgodny z POSIX) czyta jaśniej.
yoyo
27

Zwykle natywny sposób, w jaki robimy rzeczy w systemie Linux, i sposób myślenia od lewej do prawej.
Więc najpierw napisz, czego szukasz:

find / -name "*.php"

Następnie prawdopodobnie naciskasz klawisz Enter i zdajesz sobie sprawę, że otrzymujesz zbyt wiele plików z katalogów, których nie chcesz. Wykluczmy / media, aby uniknąć przeszukiwania zamontowanych dysków.
Teraz powinieneś DODATKOWAĆ następujące polecenie do poprzedniego polecenia:

-print -o -path '/media' -prune

więc końcowe polecenie to:

find / -name "*.php" -print -o -path '/media' -prune

............... | <--- Uwzględnij ---> | .................... | <- -------- Wyklucz ---------> |

Myślę, że ta struktura jest znacznie łatwiejsza i koreluje z właściwym podejściem

AmitP
źródło
3
Nie spodziewałbym się, że będzie to skuteczne - pomyślałem, że najpierw oceni lewą klauzulę przed suszonymi śliwkami, ale ku mojemu zaskoczeniu szybki test wydaje się sugerować, że findjest wystarczająco sprytny, aby -prunenajpierw przetworzyć klauzulę. Hmmm, ciekawe.
artfulrobot
Nigdy nie myślałem, że przez prawie dekadę używania GNU find! Dziękuję za to! To na pewno zmieni sposób, w jaki myślę -pruneod teraz.
Felipe Alvarez
3
@artfulrobot Czy naprawdę najpierw to przetwarza? Myślałem, że wchodzi /media, zauważając, że nie jest wywoływany, *.phpa następnie sprawdzam, czy jest on w środku /media, widząc, że tak, i dlatego pomijam całe to poddrzewo. Nadal jest od lewej do prawej, nie robi żadnej różnicy, o ile obie kontrole się nie pokrywają.
phk
26

Uważaj, że -prune nie zapobiega schodzeniu do żadnego katalogu, jak niektórzy powiedzieli. Zapobiega zejściu do katalogów pasujących do testu, do którego jest zastosowany. Być może niektóre przykłady pomogą (zobacz przykład wyrażenia regularnego na dole). Przepraszam, że to jest tak długie.

$ find . -printf "%y %p\n"    # print the file type the first time FYI
d .
f ./test
d ./dir1
d ./dir1/test
f ./dir1/test/file
f ./dir1/test/test
d ./dir1/scripts
f ./dir1/scripts/myscript.pl
f ./dir1/scripts/myscript.sh
f ./dir1/scripts/myscript.py
d ./dir2
d ./dir2/test
f ./dir2/test/file
f ./dir2/test/myscript.pl
f ./dir2/test/myscript.sh

$ find . -name test
./test
./dir1/test
./dir1/test/test
./dir2/test

$ find . -prune
.

$ find . -name test -prune
./test
./dir1/test
./dir2/test

$ find . -name test -prune -o -print
.
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2

$ find . -regex ".*/my.*p.$"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test/myscript.pl

$ find . -name test -prune -regex ".*/my.*p.$"
(no results)

$ find . -name test -prune -o -regex ".*/my.*p.$"
./test
./dir1/test
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test

$ find . -regex ".*/my.*p.$" -a -not -regex ".*test.*"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py

$ find . -not -regex ".*test.*"                   .
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2
Wstrzymano do odwołania.
źródło
jeśli również „dotkniesz ./dir1/scripts/test” (tj. masz plik „testowy”, a nie katalog, w tym wydrukowanym podkatalogu), nie zostanie wydrukowany przez find . -name test -prune -o -print: iow, -prunejest to akcja, która również działa na plikach
Olivier Dulac
10

Dodanie do porady podanej w innych odpowiedziach (nie mam przedstawiciela do tworzenia odpowiedzi) ...

W przypadku łączenia -prunez innymi wyrażeniami istnieje subtelna różnica w zachowaniu w zależności od tego, które inne wyrażenia są używane.

Przykład @Laurence Gonsalves znajdzie pliki „* .foo”, które nie znajdują się w katalogach „.snapshot”: -

find . -name .snapshot -prune -o -name '*.foo' -print

Jednak ten nieco inny skrót, być może niechcący, wyświetli również .snapshotkatalog (i wszystkie zagnieżdżone katalogi .snapshot): -

find . -name .snapshot -prune -o -name '*.foo'

Powodem jest (według strony podręcznika w moim systemie):

Jeśli dane wyrażenie nie zawiera żadnego z podstawowych znaków -exec, -ls, -ok lub -print, dane wyrażenie jest skutecznie zastępowane przez:

(podano_wyrażenie) -print

Oznacza to, że drugi przykład jest równoważny wpisaniu następującego, modyfikując w ten sposób grupę terminów:

find . \( -name .snapshot -prune -o -name '*.foo' \) -print

Widać to przynajmniej w Solarisie 5.10. Używając różnych smaków * nix przez około 10 lat, dopiero niedawno szukałem powodu, dla którego tak się dzieje.

crw
źródło
Dziękujemy za zwrócenie uwagi na różnicę między używaniem -prunezi bez -print!
mcw
3

Przycinanie to polecenie „nie powtarzaj” przy żadnym przełączniku katalogu.

Ze strony podręcznika

Jeśli nie podano -depth, to prawda; jeśli plik jest katalogiem, nie schodź do niego. Jeśli podano -depth, false; bez efektu.

Zasadniczo nie będzie odkładał się do żadnych podkatalogów.

Weź ten przykład:

Masz następujące katalogi

  • / home / test2
  • / home / test2 / test2

Jeśli uruchomisz find -name test2:

Zwróci oba katalogi

Jeśli uruchomisz find -name test2 -prune:

Zwróci tylko / home / test2, ponieważ nie zejdzie do / home / test2, aby znaleźć / home / test2 / test2

AdamW
źródło
nie w 100% prawda: jest to „przycinanie podczas dopasowywania warunku, a jeśli jest to katalog, usuń go z listy zadań, tj. nie wpisuj go również”. -prune działa również na plikach.
Olivier Dulac
2

Nie jestem w tym ekspertem (a ta strona była bardzo pomocna wraz z http://mywiki.wooledge.org/UsingFind )

Właśnie zauważyłem, -pathże ścieżka, która w pełni pasuje do ciągu / ścieżki, która pojawia się tuż pofind ( .w tych przykładach), gdzie as -namepasuje do wszystkich basename.

find . -path ./.git  -prune -o -name file  -print

blokuje katalog .git w twoim bieżącym katalogu ( jak się znajdujesz w . )

find . -name .git  -prune -o -name file  -print

blokuje rekursywnie wszystkie podkatalogi .git.

Zauważ, że ./ jest to niezwykle ważne !! -pathmusi pasować do ścieżki zakotwiczonej . lub cokolwiek nadejdzie zaraz po znalezieniu, jeśli trafisz bez niej (z drugiej strony lub „ -o”), prawdopodobnie prawdopodobnie nie jest przycinany! Byłem naiwnie tego nieświadomy i to zmusiło mnie do użycia opcji -path, gdy jest to świetne, gdy nie chcesz przycinać wszystkich podkatalogów o tej samej nazwie basename: D

Sabgenton
źródło
Zauważ, że jeśli tak mówisz find bla/, to potrzebujesz -path bla/.git (lub *zamiast tego popchnąłbyś z przodu a zachowałby się bardziej jak -name)
sabgenton
1

Pokaż wszystko, w tym sam reż, ale nie jego nudną zawartość:

find . -print -name dir -prune
Devon
źródło
0

Jeśli przeczytasz tutaj wszystkie dobre odpowiedzi, rozumiem teraz, że następujące wyniki zwracają te same wyniki:

find . -path ./dir1\*  -prune -o -print

find . -path ./dir1  -prune -o -print

find . -path ./dir1\*  -o -print
#look no prune at all!

Ale ostatni zajmie dużo więcej czasu, ponieważ wciąż szuka wszystkiego w reż1. Myślę, że prawdziwym pytaniem jest, jak -orwyeliminować niepożądane wyniki bez ich przeszukiwania.

Więc wydaje mi się, że suszona śliwka oznacza nie przyzwoite wcześniejsze mecze, ale oznaczam to jako zrobione ...

http://www.gnu.org/software/findutils/manual/html_mono/find.html „Nie dzieje się tak jednak ze względu na efekt działania„ -prune ”(który tylko zapobiega dalszemu zejściu, nie upewnia się ignorujemy ten element). Zamiast tego efekt ten wynika z użycia „-o”. Ponieważ warunek „lub” po lewej stronie się powiódł ./src/emacs, nie jest konieczne ocenianie prawej- hand-side („-print”) w ogóle dla tego konkretnego pliku. ”

Sabgenton
źródło
0

findbuduje listę plików. Stosuje predykat podany dla każdego z nich i zwraca te, które przejdą.

Ten pomysł -pruneoznaczający wykluczenie z wyników był dla mnie bardzo mylący. Możesz wykluczyć plik bez przycinania:

find -name 'bad_guy' -o -name 'good_guy' -print  // good_guy

Wszystko -pruneto zmienia zachowanie wyszukiwania. Jeśli bieżącym dopasowaniem jest katalog, jest napisane „hej find, właśnie dopasowany plik, nie schodź do niego” . Po prostu usuwa to drzewo (ale nie sam plik) z listy plików do przeszukania.

To powinno być nazwane -dont-descend.

seeker_of_bacon
źródło
0

Istnieje wiele odpowiedzi; niektóre z nich są trochę zbyt ciężkie w teorii. Zostawię, dlaczego raz potrzebowałem śliwek, więc może wyjaśnienie typu potrzeba / przykład jest komuś przydatne :)

Problem

Miałem folder z około 20 katalogami węzłów, z których każdy miał swój node_moduleskatalog zgodnie z oczekiwaniami.

Po wejściu do dowolnego projektu zobaczysz każdy ../node_modules/module. Ale wiesz jak to jest. Prawie każdy moduł ma zależności, więc to, na co patrzysz, bardziej przypominaprojectN/node_modules/moduleX/node_modules/moduleZ...

Nie chciałem utopić się z listą zależną od zależności ...

Znając -d n/ -depth n, to by mi nie pomogło, ponieważ katalog główny / pierwszy węzeł_modules, którego chciałem dla każdego projektu, był na innej głębokości, na przykład:

Projects/MysuperProjectName/project/node_modules/...
Projects/Whatshisname/version3/project/node_modules/...
Projects/project/node_modules/...
Projects/MysuperProjectName/testProject/november2015Copy/project/node_modules/...
[...]

Jak mogę uzyskać pierwszą listę ścieżek kończących się na pierwszej node_modulesi przejść do następnego projektu, aby uzyskać to samo?

Wchodzić -prune

Po dodaniu -prunenadal będziesz mieć standardowe wyszukiwanie rekurencyjne. Każda „ścieżka” jest analizowana, a każde znalezisko wypluwa się i findkopie jak dobry facet. Ale to jest szukanie node_modulesczegoś więcej, czego nie chciałem.

Tak, różnica jest taka, że w każdym z tych różnych ścieżek, -prunebędzie findprzestać kopać dalej w tym konkretnym alei kiedy znalazła swój przedmiot. W moim przypadku node_modulesfolder.

Carles Alcolea
źródło