Uruchom `grep` z wyłączeniem pliku w określonej ścieżce

12

Chcę wykluczyć plik ./test/main.cppz wyszukiwania.

Oto co widzę:

$ grep -r pattern --exclude=./test/main.cpp
./test/main.cpp:pattern
./lib/main.cpp:pattern
./src/main.cpp:pattern

Wiem, że możliwe jest uzyskanie żądanego wyniku za pomocą wielu poleceń w układzie potoków i filtrów, ale czy istnieje jakieś cytowanie / ucieczka, które pozwoli grepzrozumieć, czego natywnie chcę?

nobar
źródło
Rozwiązanie oparte na filtrowaniu danych wyjściowych nie skaluje się dobrze, ponieważ niepotrzebnie przeszukuje plik przed wykluczeniem powiązanych wyników. Problem jest powiększany, jeśli chcę wykluczyć całe katalogi (z --exclude-dir). Dlatego chciałbym, aby grep wykonał wykluczenie natywnie.
nobar
1
--exclude określa glob, a nie ścieżkę
PersianGulf

Odpowiedzi:

6

grep nie możesz tego zrobić dla pliku w jednym określonym katalogu, jeśli masz więcej plików o tej samej nazwie w różnych katalogach, zamiast tego użyj find:

find . -type f \! -path './test/main.cpp' -exec grep pattern {} \+

MichalH
źródło
Czemu ucieczki \!i \+? Wydaje się, że działa dobrze bez ukośników.
nobar
@nobar Przyzwyczaiłem się do tego, ponieważ niektóre znaki są słowami kluczowymi powłoki, więc nigdy nie będziesz zaskoczony, ponieważ nic się nie stanie, jeśli zostaną one usunięte.
MichalH
grepnie można tego zrobić, użyj findzamiast tego” - idealnie.
nobar
4

Nie sądzę, że jest to możliwe z GNU grep. Nie potrzebujesz jednak rur.

Z find:

find . ! -path ./test/main.cpp -type f -exec grep pattern {} +

Z zsh:

grep pattern ./**/*~./test/main.cpp(.)

(wyklucza ukryte pliki, a także, aby wykluczyć .git, .svn ...).

Stéphane Chazelas
źródło
2

Mógłbym napisać książkę: „The Lost Art of xargs”. Do find ... -exec … ';uruchamia grep dla każdego pliku (ale wariant ze -exec … +nie robi). Cóż, marnujemy obecnie cykle procesora, więc czemu nie, prawda? Ale jeśli problemem jest wydajność, pamięć i moc: użyj xargs:

find . -type f \! -path 'EXCLUDE-FILE' -print0 | xargs -r0 grep 'PATTERN'

GNU find„s -print0będzie NUL-terminate jego produkcja i xargs-0opcji wyróżnienia ten format jako wejście. Zapewnia to, że niezależnie od zabawnych znaków w pliku, potok nie zostanie pomylony. Ta -ropcja zapewnia, że ​​nie wystąpi błąd w przypadku, gdy findnic nie znajdzie.

Uwaga: możesz teraz wykonywać następujące czynności:

find . -type f -print0 | grep -z -v "FILENAME EXCLUDE PATTERN" | 
  xargs -r0 grep 'PATTERN'

GNU grep -zrobi to samo, co xargs -0.

Otheus
źródło
3
Kilka interesujących uwag, ale nie jestem pewien, czy masz rację co do problemu z wydajnością. Jak rozumiem, find -exec (cmd) {} +działa tak samo jak xargsi find -exec (cmd) {} \;działa tak samo jak xargs -n1. Innymi słowy, twoje oświadczenie jest poprawne tylko wtedy, gdy \;używana jest wersja.
nobar
3
Pipowanie do xargsjest mniej wydajne niż używanie -exec … +(choć nieznacznie). Żadna z odpowiedzi tutaj nawet nie wspomina -exec … \;.
Gilles „SO- przestań być zły”
1
Cóż, s - t. Umawiam się. Dzięki za komentarze i poprawki. Myślałem, że \ + to literówka. Och, spójrz, -exec ... +dodano w styczniu 2005. Tak, nie jestem nieaktualny ... w ogóle ... w ogóle.
Otheus
2

Jeśli masz findobsługę, -pathktóra została dodana do POSIX w 2008 roku, ale nadal jej brakuje w Solarisie:

find . ! -path ./test/main.cpp -type f -exec grep pattern /dev/null {} +
Cuonglm
źródło
1
Nie sądzę, żeby to zadziałało, ponieważ Nobar chce main.cpp w innych katalogach
Eric Renouf
1
czy Twój wzór nie wyklucza również pliku main.cpp ze wszystkich innych katalogów? To nie byłoby pożądane
Eric Renouf,
@EricRenouf: Oh, mój błąd, błąd w czytaniu. Zaktualizowałem moją odpowiedź.
cuonglm
@Gilles: Dlaczego -pathnie jest POSIX?
cuonglm
Ach, przepraszam, mój błąd, został dodany w 2008 roku. Nadal jednak brakuje w Solarisie.
Gilles „SO- przestań być zły”
1

Dla przypomnienia, oto podejście, które wolę:

grep pattern $(find . -type f ! -path './test/main.cpp')

Trzymając grepna początku polecenia, myślę, że jest to nieco bardziej wyraźne - a ponadto nie wyłącza greppodświetlania kolorów. W pewnym sensie użycie findw podstawianiu poleceń jest tylko sposobem na rozszerzenie / zastąpienie (ograniczonego) podzbioru grepfunkcji wyszukiwania plików .


Dla mnie find -execskładnia jest dość tajemnicza. Jedną ze złożoności find -execjest (czasem) potrzeba ucieczki przed różnymi postaciami (zwłaszcza jeśli \;jest używana pod Bash). Tylko w celu umieszczenia rzeczy w znanych kontekstach następujące dwa polecenia są w zasadzie równoważne:

find . ! -path ./test/main.cpp -type f -exec grep pattern {} +
find . ! -path ./test/main.cpp -type f -print0 |xargs -0 grep pattern

Jeśli chcesz wykluczyć podkatalogi , może być konieczne użycie znaku wieloznacznego. Nie do końca rozumiem tutaj schemat - mów o tajemniczych :

grep pattern $(find . -type f ! -path './test/main.cpp' ! -path './lib/*' )

Jeszcze jedna uwaga na temat uogólnień findopartych na rozwiązaniach do użycia w skryptach : grepWiersz poleceń powinien zawierać opcję -H/ --with-filename. W przeciwnym razie zmieni formatowanie wyjściowe pod warunkiem, że w wynikach wyszukiwania znajduje się tylko jedna nazwa pliku find. Jest to godne uwagi, ponieważ nie wydaje się konieczne, jeśli używasz grepnatywnego wyszukiwania plików (z -ropcją).

... Jeszcze lepiej jest dołączyć /dev/nulljako pierwszy plik do przeszukania. To rozwiązuje dwa problemy:

  • Zapewnia, że ​​jeśli jest jeden plik do przeszukania, greppomyśli, że są dwa pliki i używa trybu wyjścia z wieloma plikami.
  • Zapewnia, że ​​jeśli nie ma plików do przeszukania, greppomyśli, że jest jeden plik i nie zawiesi się, czekając na standardowe wejście.

Ostateczna odpowiedź to:

grep pattern /dev/null $(find . -type f ! -path './test/main.cpp')
nobar
źródło
Nie należy używać wyniku findw podstawianiu poleceń. Dzieje się tak, jeśli istnieją nazwy plików zawierające spacje lub inne znaki specjalne. Użyj find -exec, jest solidny i łatwy w użyciu.
Gilles „SO- przestań być zły”
@Gilles: Bardzo dobry punkt - również wyjście może przekraczać limity wielkości wiersza poleceń niektórych programów. Zastrzegający emptor.
nobar
Ugh. Składnia „znajdź” jest strasznie trudna. „-o” jest operatorem „lub” (również „-or” w systemie Linux), ale jego typowe użycie (na przykład z „-prune”) nie odwzorowuje koncepcyjnie pojęcia logicznego lub. Jest to funkcjonalne, a nie logiczne lub.
nobar
Innym sposobem, aby wykluczyć podkatalogi w oparciu o pasujące nazwy: find -iname "*target*" -or -name 'exclude' -prune. Cóż, to działa - przycięty katalog zostanie wyświetlony, ale nie zostanie przeszukany. Jeśli nie chcesz, aby była na liście, możesz dodać coś zbędnego! -name 'exclude'
nobar