Jak wyszukiwać pliki, w których istnieją dwa różne słowa?

14

Szukam sposobu wyszukiwania plików, w których istnieją dwa wystąpienia słów w tym samym pliku. Do tego momentu korzystałem z następujących funkcji:

find . -exec grep -l "FIND ME" {} \;

Problem, na który wpadam, polega na tym, że jeśli nie ma dokładnie jednej spacji między „ZNAJDŹ” a „ME”, wynik wyszukiwania nie daje pliku. Jak dostosować poprzedni ciąg wyszukiwania, w którym w pliku występują oba słowa „ZNAJDŹ” i „ME”, a nie „ZNAJDŹ”?

Korzystam z systemu AIX.

Chad Harrison
źródło
1
Czy słowa istnieją gdziekolwiek w pliku, czy zawsze są w tym samym wierszu?
Sobrique,
Zamiarem była ta sama linia.
Chad Harrison
Alternatywnie, jeśli słowa znajdują się w tym samym wierszu, należy użyć wyrażenia regularnego z grep -E/, egrepktóre opisuje wszystkie wzorce, którymi jesteś zainteresowany (i +zamiast tego, ;jeśli twoje wyszukiwanie ma wsparcie +.
MattBianco 22.09.17

Odpowiedzi:

21

Za pomocą narzędzi GNU:

find . -type f  -exec grep -lZ FIND {} + | xargs -r0 grep -l ME

Możesz zrobić standardowo:

find . -type f -exec grep -q FIND {} \; -exec grep -l ME {} \;

Ale to uruchomiłoby dwa greps na plik. Aby uniknąć uruchamiania tylu grepsekund i nadal być przenośnym, jednocześnie pozwalając na dowolny znak w nazwie pliku, możesz:

convert_to_xargs() {
  sed "s/[[:blank:]\"\']/\\\\&/g" | awk '
    {
      if (NR > 1) {
        printf "%s", line
        if (!index($0, "//")) printf "\\"
        print ""
      }
      line = $0
    }'
    END { print line }'
}

find .//. -type f |
  convert_to_xargs |
  xargs grep -l FIND |
  convert_to_xargs |
  xargs grep -l ME

Chodzi o to, aby przekonwertować dane wyjściowe findna format odpowiedni dla xargs (który oczekuje spacji (SPC / TAB / NL i innych spacji z twojego regionu z pewnymi implementacjami xargs) oddzielonej listy słów, w których pojedyncze, podwójne cudzysłowy i ukośniki odwrotne mogą unikaj pustych miejsc i siebie nawzajem).

Zasadniczo nie można przetworzyć wyniku find -print, ponieważ oddziela on nazwy plików znakiem nowej linii i nie zmienia znaków nowego wiersza znajdujących się w nazwach plików. Na przykład, jeśli zobaczymy:

./a
./b

Nie mamy sposobu, aby wiedzieć, czy jest to jeden plik wywoływany bw katalogu o nazwie, a<NL>.czy to dwa pliki ai b.

Używając .//., ponieważ //nie może pojawić się inaczej w ścieżce pliku jako wyjście find(ponieważ nie ma czegoś takiego jak katalog z pustą nazwą i /nie jest dozwolony w nazwie pliku), wiemy, że jeśli widzimy wiersz zawierający //, to jest to pierwszy wiersz nowej nazwy pliku. Możemy więc użyć tego awkpolecenia, aby uciec od wszystkich znaków nowego wiersza oprócz tych poprzedzających te wiersze.

Jeśli weźmiemy powyższy przykład, findwynik byłby w pierwszym przypadku (jeden plik):

.//a
./b

Który awk ucieka do:

.//a\
./b

To xargspostrzega to jako jeden argument. A w drugim przypadku (dwa pliki):

.//a
.//b

Co awkby pozostało bez zmian, więc xargswidzimy dwa argumenty.

Stéphane Chazelas
źródło
Dlaczego nie użyć find ... -print0i grep --nullzamiast tego?
razzed
@razzed, nie jestem pewien, co masz na myśli. grep --null(aka -Z) jest używane w pierwszym, ale jest rozszerzeniem GNU. -print0(inne rozszerzenie GNU) tutaj by nie pomogło.
Stéphane Chazelas
Dzięki. Chciałbym owinąć kod powłoki w skrypt, który pobiera katalog wyszukiwania jako argument z wiersza poleceń. Nie jestem jeszcze pewien, co to .//.znaczy, i zastanawiam się, jak mogę to zmienić, aby zaakceptować argument z wiersza poleceń, powiedzmy $1?
Tim
Dzięki. W polecenia, jest to niezbędne do korzystania -print0z finda -0z xargs?
Tim
@Tim, nie jestem pewien, co masz na myśli. find -print0Nigdzie nie używam w mojej odpowiedzi.
Stéphane Chazelas,
8

Jeśli pliki znajdują się w jednym katalogu, a ich nazwa nie zawierają miejsca, tabulacjami, *, ?ani [znaków i nie zaczynać -ani .będzie to uzyskać listę plików zawierających ME, następnie zawęzić, że aż do tych, które zawierają również ZNAJDŹ.

grep -l FIND `grep -l ME *`
użytkownik45529
źródło
TO potrzebuje więcej głosów pozytywnych !! O wiele bardziej elegancki niż odpowiedź „zaakceptowana”. Pracował dla mnie.
roblogic,
Po prostu zrobiłem grep -l CategoryLinearAxis `grep -l labelJsFunction *`, szukając plików, które zawierają oba atrybuty. Co za doskonały sposób to zrobić. +1
WEBjuju
3

Z awktobą możesz również uruchomić:

find . -type f  -exec awk 'BEGIN{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; END{if (cx > 0 && cy > 0) print FILENAME}' {} \;

Używa cxi cydo liczenia FINDodpowiednio pasujących linii ME. W ENDbloku, jeśli oba liczniki> 0, drukuje FILENAME.
Byłoby to szybsze / bardziej wydajne dzięki gnu awk:

find . -type f  -exec gawk 'BEGINFILE{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; ENDFILE{if (cx > 0 && cy > 0) print FILENAME}' {} +
don_crissti
źródło
2

Lub użyj egrep -elub w grep -Eten sposób:

find . -type f -exec egrep -le '(ME.*FIND|FIND.*ME)' {} \;

lub

find . -type f -exec grep -lE '(ME.*FIND|FIND.*ME)' {} +

Te +marki znaleźć (jeśli jest obsługiwany) dodać kilka plików (ścieżka) nazw jako argumenty do polecenia będącego -execed. Oszczędza to procesy i jest o wiele szybsze niż w przypadku, \;gdy wywołuje polecenie raz dla każdego znalezionego pliku.

-type f dopasowuje tylko pliki, aby uniknąć grepowania w katalogu.

'(ME.*FIND|FIND.*ME)'to wyrażenie regularne pasujące do dowolnej linii zawierającej „ME”, po której następuje „FIND” lub „FIND”, po której następuje „ME”. (pojedyncze cudzysłowy, aby zapobiec interpretacji znaków specjalnych przez powłokę).

Dodaj a -ido greppolecenia, aby rozróżniać wielkość liter.

Aby dopasować tylko linie, w których „ZNAJDŹ” znajduje się przed „ME”, użyj 'FIND.*ME'.

Aby wymagać spacji (1 lub więcej, ale nic więcej) między słowami: 'FIND +ME'

Aby zezwolić na spacje (0 lub więcej, ale nic więcej) między słowami: 'FIND *ME'

Kombinacje są nieograniczone z wyrażeniami regularnymi i pod warunkiem, że jesteś zainteresowany dopasowywaniem tylko w rzędzie na raz, egrep jest bardzo potężny.

MattBianco
źródło
Czy większość greps nie obsługuje „-r”? To wyeliminowałoby „znajdź”, ale w drzewie mogą znajdować się gniazda lub inne pliki, które nie są zwykłe.
stolenmoment
OP używa systemu AIX i miał findw pytaniu.
MattBianco,
0

Patrząc na przyjętą odpowiedź, wydaje się bardziej złożona niż powinna być. Wersje GNU findi grepi xargswsparcia nul struny. To tak proste, jak:

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l ME

Możesz zmodyfikować swoje findpolecenie, aby filtrować do żądanych plików, i działa z nazwami plików zawierających dowolny znak; bez dodatkowej złożoności sedanalizy. Jeśli chcesz dalej przetwarzać pliki, dodaj kolejny --nulldo ostatniegogrep

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l --null ME | xargs -0 echo

I jako funkcja:

find_strings() {
    find . -type f -print0 | xargs -0 grep -l --null "$1" | xargs -0 grep -l "$2"
}

Oczywiście skorzystaj z zaakceptowanej odpowiedzi, jeśli nie korzystasz z wersji GNU tych narzędzi.

razzed
źródło
1
--null, --print0, -0Są wszystkie rozszerzenia GNU. Chociaż niektóre z nich znajdują się obecnie w innych implementacjach, wciąż nie są przenośne i nie są w standardzie POSIX ani Unix.
Stéphane Chazelas