Jak mogę grepować dla wielu wzorów z wzorem mającym znak rury?

623

Chcę znaleźć wszystkie wiersze w kilku plikach, które pasują do jednego z dwóch wzorów. Próbowałem znaleźć wzorce, których szukam, pisząc

grep (foo|bar) *.txt

ale powłoka interpretuje |potok i narzeka, gdy barnie jest plikiem wykonywalnym.

Jak mogę grep dla wielu wzorców w tym samym zestawie plików?

Dan
źródło
możliwy duplikat Grep: jak dodać warunek „LUB”?
phuclv
grep 'word1 \ | word2 \ | word3' / path / to / file
lambodar

Odpowiedzi:

861

Najpierw musisz chronić wzór przed rozszerzeniem przez powłokę. Najłatwiej to zrobić, umieszczając wokół niego pojedyncze cudzysłowy. Pojedyncze cudzysłowy zapobiegają rozszerzaniu czegokolwiek między nimi (w tym ukośników odwrotnych); jedyne, czego nie możesz zrobić, to mieć pojedyncze cudzysłowy we wzorze.

grep 'foo*' *.txt

Jeśli potrzebujesz pojedynczego cytatu, możesz zapisać go jako '\''(literał końca łańcucha, cytat literału, literał ciągu otwartego).

grep 'foo*'\''bar' *.txt

Po drugie, grep obsługuje dwie składnie wzorców. Stara, domyślna składnia ( podstawowe wyrażenia regularne ) nie obsługuje |operatora alternation ( ), chociaż niektóre wersje mają go jako rozszerzenie, ale napisane odwrotnym ukośnikiem.

grep 'foo\|bar' *.txt

Przenośnym sposobem jest użycie nowszej składni, rozszerzonych wyrażeń regularnych . Musisz przekazać -Eopcję, aby grepją wybrać. W Linuksie możesz także pisać egrepzamiast grep -E(w innych jednorożcach możesz zrobić z tego alias).

grep -E 'foo|bar' *.txt

Inną możliwością, gdy szukasz tylko jednego z kilku wzorców (w przeciwieństwie do budowania złożonego wzorca przy użyciu rozłączenia), jest przekazanie wielu wzorców do grep. Możesz to zrobić, poprzedzając każdy wzór -eopcją.

grep -e foo -e bar *.txt
Gilles
źródło
18
Na marginesie - kiedy wzory są naprawione, powinieneś naprawdę nabrać nawyku fgreplub grep -F, w przypadku małych wzorów różnica będzie znikoma, ale wraz z upływem czasu korzyści zaczną się pokazywać ...
TC1
7
@ TC1 fgrep jest przestarzałe według strony
podręcznika użytkownika
18
@ TC1 To, czy grep -Frzeczywista poprawa wydajności zależy od implementacji grep: niektóre z nich i tak stosują ten sam algorytm, więc -Fma to wpływ tylko na czas analizowania wzorca, a nie na wyszukiwanie czasu. Na przykład GNU grep nie jest szybszy -F(ma również błąd, który grep -Fspowalnia w lokalizacjach wielobajtowych - ten sam stały wzorzec grepjest w rzeczywistości znacznie szybszy!). Z drugiej strony, BusyBox grep bardzo zyskuje -Fna dużych plikach.
Gilles
4
Być może należy wspomnieć, że w przypadku bardziej skomplikowanych wzorców, w których naprzemienność ma być tylko częścią wyrażenia regularnego, można go zgrupować z „\ (” i „\)” (znak ucieczki dotyczy domyślnych „podstawowych wyrażeń regularnych” ) (?).
Peter Mortensen
4
Zauważ, że egreppoprzedza grep -E. Nie jest on specyficzny dla GNU (z pewnością nie ma nic wspólnego z Linuksem). W rzeczywistości nadal znajdziesz systemy takie jak Solaris, w których domyślna grepnadal nie obsługuje -E.
Stéphane Chazelas
89
egrep "foo|bar" *.txt

lub

grep "foo\|bar" *.txt
grep -E "foo|bar" *.txt

wybiórczo powołując się na stronę man gnu-grep:

   -E, --extended-regexp
          Interpret PATTERN as an extended regular expression (ERE, see below).  (-E is specified by POSIX.)

Matching Control
   -e PATTERN, --regexp=PATTERN
          Use PATTERN as the pattern.  This can be used to specify multiple search patterns, or to protect  a  pattern
          beginning with a hyphen (-).  (-e is specified by POSIX.)

(...)

   grep understands two different versions of regular expression syntax: basic and extended.”  In  GNU grep,  there
   is  no  difference  in  available  functionality  using  either  syntax.   In  other implementations, basic regular
   expressions are less powerful.  The following description applies to extended regular expressions; differences  for
   basic regular expressions are summarized afterwards.

Na początku nie czytałem dalej, więc nie rozpoznałem subtelnych różnic:

Basic vs Extended Regular Expressions
   In basic regular expressions the meta-characters ?, +, {, |, (, and ) lose their special meaning; instead  use  the
   backslashed versions \?, \+, \{, \|, \(, and \).

Zawsze używałem egrep i niepotrzebnie parens, ponieważ uczyłem się na przykładach. Teraz nauczyłem się czegoś nowego. :)

nieznany użytkownik
źródło
22

Jak powiedział TC1, -Fwydaje się użyteczną opcją:

$> cat text
some text
foo
another text
bar
end of file

$> patterns="foo
bar" 

$> grep -F "${patterns}" text
foo
bar
ДМИТРИЙ МАЛИКОВ
źródło
1
@poige Nie wiedziałem o opcji $ 'foo \ nbar', nie jestem pewien, jak działa tutaj ekspansja, muszę spojrzeć w górę, ale dziękuję, to jest naprawdę przydatne.
haridsv
Miły! Ta opcja sprawia również, że działa znacznie szybciej (ponieważ wyłącza wyrażenia regularne).
qwertzguy
15

Po pierwsze, musisz używać cudzysłowów dla znaków specjalnych. Po drugie, mimo to grepnie zrozumie bezpośrednio alternacji; musisz użyć egreplub ( greptylko z GNU ) grep -E.

egrep 'foo|bar' *.txt

(Nawiasy są niepotrzebne, chyba że alternacja jest częścią większego wyrażenia regularnego.)

geekozaur
źródło
4
W rzeczywistości grep -Ejest bardziej standardowy niż egrep.
jw013
8

Jeśli nie potrzebujesz wyrażeń regularnych, jest to znacznie szybsze w użyciu fgreplub grep -Fz wieloma parametrami -e, takimi jak:

fgrep -efoo -ebar *.txt

fgrep(alternatywnie grep -F) jest znacznie szybszy niż regularne grep, ponieważ szuka stałych ciągów zamiast wyrażeń regularnych.

Moustafa Elqabbany
źródło
4
Zobacz także komentarze na tej stronie, które fgrepsą przestarzałe.
phk
6

Możesz wypróbować poniższe polecenie, aby uzyskać wynik:

egrep 'rose.*lotus|lotus.*rose' some_file
Abhishek
źródło
3

Tani i wesoły sposób na grep dla wielu wzorów:

$ echo "foo" > ewq ; echo "bar" >> ewq ; grep -H -f ewq *.txt ; rm ewq
DHDHDHD
źródło
Może skorzystać z wyjaśnienia.
Peter Mortensen,
2
Wyjaśnienie jest takie, że -fopcja grep pobiera plik z wieloma wzorami. Zamiast tworzyć plik tymczasowy (który możesz później zapomnieć usunąć), po prostu użyj podstawienia procesu powłoki:grep -f <(echo foo; echo bar) *.txt
Jakob
3

Pipe ( |) jest specjalnym znakiem powłoki, więc albo należy go zmienić ( \|), albo cytować zgodnie z instrukcją ( man bash):

Cytowanie służy do usuwania specjalnego znaczenia niektórych znaków lub słów z powłoki. Można go użyć do wyłączenia specjalnego traktowania znaków specjalnych, aby zapobiec rozpoznawaniu słów zastrzeżonych jako takich i zapobiec rozszerzaniu parametrów.

Umieszczanie znaków w podwójnych cudzysłowach zachowuje dosłowną wartość wszystkich znaków w cudzysłowach

Niecytowany ukośnik odwrotny ( \) to znak zmiany znaczenia.

Zobacz: Które postacie należy uciec w Bash?

Oto kilka przykładów (użycie niewymienionych jeszcze narzędzi):

  • Używanie ripgrep:

    • rg "foo|bar" *.txt
    • rg -e foo -e bar *.txt
  • Używanie git grep:

    • git grep --no-index -e foo --or -e bar

      Uwaga: Wspiera on także wyrażeń logicznych takich jak --and, --ori --not.

Aby dowiedzieć się więcej o operacji AND na wiersz, zobacz: Jak uruchomić grep z wieloma wzorcami AND?

Aby dowiedzieć się więcej o operacji AND na plik, zobacz: Jak sprawdzić wszystkie ciągi znaków lub wyrażenia regularne w pliku?

kenorb
źródło
3

Miałem dzienniki dostępu, w których daty były głupio sformatowane: [30 / Jun / 2013: 08: 00: 45 +0200]

Ale musiałem wyświetlić go jako: 30 / Jun / 2013 08:00:45

Problem polega na tym, że używając „OR” w mojej instrukcji grep, otrzymywałem dwa wyrażenia dopasowania w dwóch osobnych wierszach.

Oto rozwiązanie:

grep -in myURL_of_interest  *access.log  | \
grep -Eo '(\b[[:digit:]]{2}/[[:upper:]][[:lower:]]{2}/[[:digit:]]{4}|[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\b)'   \
| paste - - -d" " > MyAccess.log
tsmets
źródło
2

TL; DR: jeśli chcesz zrobić więcej rzeczy po dopasowaniu jednego z wielu wzorców, dołącz je jak w \(pattern1\|pattern2\)

przykład: chcę znaleźć wszystkie miejsca, w których zmienna zawierająca nazwę „data” jest zdefiniowana jako String lub int. (np. „int cronDate =” lub „String textFormattedDateStamp =”):

cat myfile | grep '\(int\|String\) [a-zA-Z_]*date[a-zA-Z_]* =' 

Dzięki grep -Enie musisz uciec od nawiasów ani potoku, tzn.grep -E '(int|String) [a-zA-Z_]*date[a-zA-Z_]* ='

jeremysprofile
źródło
1

To działa dla mnie

root@gateway:/home/sshuser# aws ec2 describe-instances --instance-ids i-2db0459d |grep 'STATE\|TAG'

**STATE**   80      stopped

**STATE**REASON     Client.UserInitiatedShutdown    Client.UserInitiatedShutdown: User initiated shutdown

**TAGS**    Name    Magento-Testing root@gateway:/home/sshuser#
Mansur Ali
źródło
1

Można to zrobić na wiele sposobów.

  1. grep 'foo\|bar' *.txt
  2. egrep 'foo|bar' *.txt
  3. find . -maxdepth 1 -type f -name "*.txt" | xargs grep 'foo\|bar'
  4. find . -maxdepth 1 -type f -name "*.txt" | xargs egrep 'foo|bar'

Trzecia i czwarta opcja będzie grepować tylko w plikach i unikać katalogów posiadających .txtich nazwy.
Tak więc, zgodnie z twoim przypadkiem użycia, możesz użyć dowolnej z wyżej wymienionych opcji.
Dzięki!!

Bhagyesh Dudhediya
źródło
0

aby dodać do odpowiedzi @ geekozaura , jeśli masz wiele wzorów zawierających tabulatory i spację, użyj następującego polecenia

grep -E "foo[[:blank:]]|bar[[:blank:]]"

gdzie [[:blank:]]jest klasą RE, która reprezentuje spację lub znak tabulacji

Zespół Fuseteam
źródło