Rekurencyjnie wyszukiwać wzór / tekst tylko w podanej nazwie pliku katalogu?

16

Mam katalog (np. abc/def/efg) Z wieloma podkatalogami (np .::) abc/def/efg/(1..300). Wszystkie te podkatalogi mają wspólny plik (np file.txt.). Chcę wyszukiwać ciąg tylko w tym z file.txtwyłączeniem innych plików. W jaki sposób mogę to zrobić?

Użyłem grep -arin "pattern" *, ale jest bardzo powolny, jeśli mamy wiele podkatalogów i plików.

Rajesh Keladimath
źródło
Powiązane (w systemach Unix i Linux ): znajdź i
powtórz

Odpowiedzi:

21

W katalogu nadrzędnym można było używać, finda następnie uruchamiać greptylko te pliki:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +
Zanna
źródło
2
Sugeruję również przekazanie -Hdo, grepaby w przypadkach, gdy przekazywana jest tylko jedna ścieżka, ścieżka ta jest nadal drukowana (zamiast tylko pasujących wierszy z pliku).
Eliah Kagan
24

Możesz także użyć globstar.

Budowanie greppoleceń za pomocą find, jak w odpowiedzi Zanny , jest bardzo solidnym, wszechstronnym i przenośnym sposobem na to (patrz także odpowiedź Sudodusa ). A muru opublikowało doskonałe podejście do korzystania grepz --includeopcji . Ale jeśli chcesz użyć tylko greppolecenia i powłoki, możesz to zrobić na inny sposób - możesz sprawić, że sama powłoka wykona niezbędną rekursję :

shopt -s globstar   # you can skip this if you already have globstar turned on
grep -H 'pattern' **/file.txt

Te -Hmarki flag grepwyświetlić nazwę pliku, nawet jeśli tylko jeden pasujący plik zostanie znaleziony. Można przekazać -a, -ioraz -nflagi (z twojego przykładzie) grep, a także, jeśli to, co trzeba. Ale nie zaliczaj -rlub -Rpodczas korzystania z tej metody. Jest to powłoka, która powraca do katalogów w rozszerzaniu wzorca globu zawierającego **, a niegrep .

Te instrukcje są specyficzne dla powłoki Bash. Bash jest domyślną powłoką użytkownika w Ubuntu (i większości innych systemów operacyjnych GNU / Linux), więc jeśli korzystasz z Ubuntu i nie wiesz, co to jest twoja powłoka, to prawie na pewno Bash. Chociaż popularne powłoki zwykle obsługują **globusy przeszukujące katalogi , nie zawsze działają w ten sam sposób. Aby uzyskać więcej informacji, zobacz Stéphane Chazelas „s doskonałą odpowiedź do wyniku ls * ls ls ** i *** na Unix.SE .

Jak to działa

Włączenie opcji powłoki bash globstar powoduje, że ścieżki dopasowania zawierające separator katalogów ( ). Jest to zatem glob rekursujący katalogi. W szczególności, jak wyjaśniono:**/man bash

Gdy opcja powłoki globstar jest włączona, a * jest używany w kontekście rozszerzenia nazwy ścieżki, dwa sąsiednie * użyte jako pojedynczy wzorzec będą pasować do wszystkich plików oraz zero lub więcej katalogów i podkatalogów. Jeśli po nich następuje znak /, dwa sąsiadujące * s będą pasować tylko do katalogów i podkatalogów.

Powinieneś być z tym ostrożny, ponieważ możesz uruchamiać polecenia, które modyfikują lub usuwają znacznie więcej plików, niż masz zamiar, zwłaszcza jeśli piszesz, **gdy masz zamiar pisać *. (Jest to bezpieczne w tym poleceniu, które nie zmienia żadnych plików.) shopt -u globstarWyłącza opcję powłoki globstar.

Istnieje kilka praktycznych różnic między globstar a find.

findjest znacznie bardziej wszechstronny niż globstar. Wszystko, co możesz zrobić z globstar, możesz zrobić również z findpoleceniem. Lubię globstar i czasami jest to wygodniejsze, ale globstar nie jest ogólną alternatywą dla find.

Powyższa metoda nie sprawdza katalogów, których nazwy zaczynają się od .. Czasami nie chcesz rekursować takich folderów, ale czasem tak.

Podobnie jak w przypadku zwykłego globu, powłoka buduje listę wszystkich pasujących ścieżek i przekazuje je jako argumenty do polecenia ( grep) zamiast samego globu. Jeśli masz tak wiele plików o nazwie, file.txtże wynikowe polecenie byłoby zbyt długie, aby system mógł je wykonać, wówczas powyższa metoda zawiedzie. W praktyce potrzebujesz (przynajmniej) tysięcy takich plików, ale może się zdarzyć.

Stosowane metody findnie podlegają tym ograniczeniom, ponieważ:

  • Sposób Zanny buduje i uruchamia greppolecenie z potencjalnie wieloma argumentami ścieżki. Ale jeśli znaleziono więcej plików, niż można je wyświetlić w jednej ścieżce, akcja +-terminated -execuruchamia polecenie z niektórymi ścieżkami, a następnie uruchamia je ponownie z kilkoma ścieżkami i tak dalej. W przypadku grepwprowadzania ciągu w wielu plikach powoduje to prawidłowe zachowanie.

    Podobnie jak opisana tutaj metoda globstar, drukuje ona wszystkie pasujące linie, z dołączonymi do nich ścieżkami.

  • Droga sudodusa przebiega greposobno dla każdego file.txtznalezionego. Jeśli jest wiele plików, może być wolniejsze niż niektóre inne metody, ale działa.

    Ta metoda wyszukuje pliki i drukuje ich ścieżki, a następnie pasujące linie, jeśli takie istnieją. Jest to inny format wyjściowy niż format utworzony przez moją metodę Zanna i Muru .

Uzyskiwanie koloru find

Jedną z bezpośrednich korzyści płynących z używania globstar jest to, że domyślnie na Ubuntu grepprodukuje kolorowe wydruki. Ale można łatwo dostać się z tym findteż .

Konta użytkowników w Ubuntu są tworzone za pomocą aliasu, który sprawia, że grepnaprawdę działa grep --color=auto(uruchom, alias grepaby zobaczyć). To dobrze, że aliasy są dość dużo tylko rozszerzać, gdy wydasz je interaktywnie , ale oznacza to, że jeśli chcesz find, aby wywołać grepz --colorflagą, musisz napisać to wyraźnie. Na przykład:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +
Eliah Kagan
źródło
Możesz lepiej powiedzieć, że musisz użyć bashpowłoki, aby to zadziałało. Ty nie mów tego w sposób dorozumiany „opcją powłoki globstar bash”, ale może być łatwo pominięte przez ludzi zbyt szybko czyta.
Stig Hemmer
Usunąłem swoją odpowiedź, ponieważ spowodowała wiele krytycznych komentarzy. Więc powinieneś usunąć odniesienie do tego w swojej odpowiedzi.
sudodus
@StigHemmer Dzięki - wyjaśniłem, że nie wszystkie powłoki mają tę funkcję. Chociaż wiele powłok (nie tylko bash) obsługuje globs przeszukujące katalogi **, twoja podstawowa krytyka jest poprawna: prezentacja **w tej odpowiedzi jest specyficzna dla bash, z shopt tylko bash, a termin „globstar” to (myślę) bash i tylko tcsh. Zastanawiałem się nad tym pierwotnie z powodu tych złożoności, ale masz rację, że jest to trochę mylące. Zamiast omawiać go szczegółowo w tej odpowiedzi, podłączyłem do innego (dość dokładnego) postu, który wykonuje ciężkie podnoszenie.
Eliah Kagan
@sudodus Zrobiłem to, ale mam nadzieję, że to tymczasowe. Ja i inni uznaliśmy twoją odpowiedź za cenną. To prawda, -eże nie należy go stosować do ścieżek, ale można to łatwo naprawić. W przypadku pierwszego polecenia po prostu pomiń -e. Po drugie użyj find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;lub find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;. Użytkownicy czasem wolą twoją drogę (z -eustalonym użyciem) od innych, którzy drukują jedną ścieżkę na pasującą linię ; twoja drukuje jedną ścieżkę na znaleziony plik, a następnie grepwyniki.
Eliah Kagan
@sudodus Więc grepsamo nie zrobi tego, co robisz. Niektóre inne krytyki również były błędne. grep -Hprowadzony przez -execwoli nie kolorowania bez --color(lub GREP_COLOR). IEEE 1003.1-2008 nie gwarantuje {}rozszerzenia ##### {}:, ale Ubuntu ma funkcję GNU find, która działa . Jeśli wszystko jest w porządku, zredaguję Twój post, aby naprawić -ebłąd (i wyjaśnię jego przypadek użycia), i zobaczysz, czy chcesz cofnąć usunięcie. (Mam przedstawiciela do przeglądania / edytowania usuniętych postów.)
Eliah Kagan
18

Nie potrzebujesz findtego; grepradzi sobie z tym doskonale doskonale:

grep "pattern" . -airn --include="file.txt"

Od man grep:

--exclude=GLOB
      Skip  files  whose  base  name  matches  GLOB  (using   wildcard
      matching).   A  file-name  glob  can  use  *,  ?,  and [...]  as
      wildcards, and \ to quote  a  wildcard  or  backslash  character
      literally.

--exclude-from=FILE
      Skip  files  whose  base name matches any of the file-name globs
      read from FILE  (using  wildcard  matching  as  described  under
      --exclude).

--exclude-dir=DIR
      Exclude  directories  matching  the  pattern  DIR from recursive
      searches.

--include=GLOB
      Search  only  files whose base name matches GLOB (using wildcard
      matching as described under --exclude).
muru
źródło
Fajnie - wydaje się, że to najlepszy sposób. Prosty i wydajny. Chciałbym wiedzieć o tej metodzie (lub pomyślałem o sprawdzeniu strony). Dzięki!
Eliah Kagan
@EliahKagan Jestem bardziej zaskoczony, że Zanna tego nie opublikowała - jakiś czas temu pokazałem przykład tej opcji dla innej odpowiedzi. :)
muru
2
powolny uczeń, niestety, ale w końcu się tam dostaję, twoje nauki nie są dla mnie całkowicie zmarnowane;)
Zanna
Jest to bardzo proste i łatwe do zapamiętania. Dziękuję Ci.
Rajesh Keladimath
Zgadzam się, że to najlepsza odpowiedź. Czy powinienem usunąć moją odpowiedź, aby zmniejszyć zamieszanie, czy pozwolić, aby pozostało, aby pokazać, że istnieją alternatywy i co można zrobić za pomocąfind?
sudodus
8

Sposób podany w odpowiedzi Muru jest , biegania grepz --includeflagą, aby określić nazwę pliku, jest często najlepszym wyborem. Można to jednak zrobić również za pomocą find.

Podejście w tej odpowiedzi wykorzystuje się finddo uruchomienia greposobno dla każdego znalezionego pliku i wypisuje ścieżkę do każdego pliku dokładnie raz , powyżej pasujących wierszy znalezionych w każdym pliku. (Metody, które drukują ścieżkę przed każdą pasującą linią, są omówione w innych odpowiedziach).


Możesz zmienić katalog na górę drzewa katalogów, w którym masz te pliki. Następnie uruchomić:

find . -name "file.txt" -type f -exec echo "##### {}:" \; -exec grep -i "pattern" {} \;

Spowoduje to wydrukowanie ścieżki (względem bieżącego katalogu .i samego pliku) każdego nazwanego pliku file.txt, a następnie wszystkich pasujących wierszy w pliku. Działa {}to, ponieważ jest symbolem zastępczym dla znalezionego pliku. Ścieżka każdego pliku jest oddzielana od jego zawartości, ponieważ jest poprzedzona znakiem #####i jest drukowana tylko raz, przed pasującymi wierszami z tego pliku. (Wywołane pliki, file.txtktóre nie zawierają żadnych dopasowań, nadal mają wydrukowane ścieżki). Może się okazać, że dane wyjściowe są mniej zagracone niż w przypadku metod, które drukują ścieżkę na początku każdej pasującej linii.

Używanie w findten sposób prawie zawsze będzie szybsze niż uruchamianie grepna każdym pliku ( grep -arin "pattern" *), ponieważ findwyszukuje pliki o poprawnej nazwie i pomija wszystkie inne pliki.

Ubuntu korzysta z wyszukiwania GNU , które zawsze rozwija się, {}nawet jeśli pojawia się w większym ciągu , np ##### {}:. Jeśli potrzebujesz komendy do pracy findw systemach, które mogą tego nie obsługiwać lub wolisz korzystać z -execakcji tylko wtedy, gdy jest to absolutnie konieczne, możesz użyć:

find . -name "file.txt" -type f -printf '##### %p:\n' -exec grep -i "pattern" {} \;

Aby ułatwić odczytanie danych wyjściowych , możesz użyć sekwencji ucieczki ANSI, aby uzyskać kolorowe nazwy plików. To sprawia, że ​​nagłówek ścieżki do każdego pliku wyróżnia się lepiej niż pasujące linie, które są drukowane pod nim:

find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;

To powoduje, że twoja powłoka przekształca kod zmiany znaczenia na zielony w rzeczywistą sekwencję zmiany znaczenia, która wytwarza kolor zielony w terminalu, i robi to samo z kodem zmiany znaczenia dla normalnego koloru. Te znaki ucieczki są przekazywane do find, który używa ich podczas drukowania nazwy pliku. ( $' 'Cytat jest konieczne tutaj, ponieważ find„s -printfdziałania nie rozpoznaje \einterpretowania kody ucieczki ANSI).

Jeśli wolisz, możesz zamiast korzystać -execz systemu printfdowodzenia (który obsługuje \e). Kolejnym sposobem na zrobienie tego samego jest:

find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;
sudodus
źródło
Miałem zamiar zrobić „pętlę for” z tablicą i nie myślałem o natywnej opcji exec z find. Dobry! Ale myślę, że użycie kropki zlokalizuje cię w katalogu, w którym już jesteś. Popraw mnie, jeśli się mylę. Czy nie lepiej byłoby określić bezpośrednio parsowanie w kolejności wyszukiwania? find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
kcdtv
Jasne, że to wyeliminuje polecenie cd abc/def/efg„zmień katalog” :-)
sudodus
(1) Dlaczego określasz -eopcję echo? Spowoduje to, że zmieni on nazwy plików zawierające ukośniki odwrotne. (2) Przy użyciu {}jako część argument nie gwarantuje pracę. Lepiej byłoby powiedzieć -exec echo "#####" {} \;lub -exec printf "##### %s:\n" {} \;. (3) Dlaczego nie po prostu użyć -printlub -printf? (4) Zastanów się także grep -H.
G-Man mówi „Przywróć Monikę”
@ G-man, 1) Ponieważ pierwotnie użyłem koloru ANSI: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) Być może masz rację, ale jak na razie to działa. 3) -print i -printf są również alternatywami. 4) To już jest w głównej odpowiedzi. - W każdym razie jesteś mile widziany z własną odpowiedzią :-)
sudodus
Nie potrzebujesz dwóch -execpołączeń. Wystarczy użyć grep -H, aby wydrukować nazwę pliku (w kolorze), a także dopasowany tekst.
terdon
0

Aby wskazać, że jeśli warunki pytania mogą być wzięte z literatury, możesz użyć bezpośredniego grep:

grep 'pattern' abc/def/efg/*/file.txt

lub

grep 'pattern' abc/def/efg/{1..300}/file.txt

źródło