Come posso grep per più pattern con pattern con un carattere pipe?


623

Voglio trovare tutte le righe in diversi file che corrispondono a uno dei due modelli. Ho provato a trovare gli schemi che sto cercando digitando

grep (foo|bar) *.txt

ma la shell la interpreta |come una pipe e si lamenta quando barnon è un eseguibile.

Come posso grep per più pattern nello stesso set di file?



grep 'word1 \ | word2 \ | word3' / path / to / file
lambodar

Risposte:


860

Innanzitutto, è necessario proteggere il pattern dall'espansione della shell. Il modo più semplice per farlo è quello di racchiuderlo tra virgolette singole. Le virgolette singole impediscono l'espansione di qualsiasi cosa tra loro (comprese le barre rovesciate); l'unica cosa che non puoi fare allora è avere singole virgolette nel modello.

grep 'foo*' *.txt

Se hai bisogno di una virgoletta singola, puoi scriverla come '\''(letterale stringa finale, virgoletta letterale, letterale stringa aperta).

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

In secondo luogo, grep supporta due sintassi per i pattern. La vecchia sintassi predefinita ( espressioni regolari di base ) non supporta l' |operatore alternation ( ), anche se alcune versioni lo hanno come estensione, ma scritto con una barra rovesciata.

grep 'foo\|bar' *.txt

Il modo portatile è usare la sintassi più recente, espressioni regolari estese . Devi selezionare l' -Eopzione grepper selezionarla. Su Linux, puoi anche digitare egrepinvece di grep -E(su altri unices, puoi renderlo un alias).

grep -E 'foo|bar' *.txt

Un'altra possibilità quando stai solo cercando uno dei diversi modelli (invece di costruire uno schema complesso usando la disgiunzione) è passare più schemi a grep. Puoi farlo precedendo ogni modello con l' -eopzione.

grep -e foo -e bar *.txt

18
Come sidenote - quando i modelli sono fissi, dovresti davvero prendere l'abitudine fgrepo grep -F, per i piccoli modelli la differenza sarà trascurabile ma man mano che si allungano, i benefici iniziano a mostrare ...
TC1

7
@ TC1 fgrep è deprecato secondo la pagina man
ramn

18
@ TC1 Il fatto che grep -Fabbia un effettivo vantaggio in termini di prestazioni dipende dall'implementazione grep: alcuni applicano comunque lo stesso algoritmo, quindi ciò -Ffa la differenza solo per il tempo impiegato per l'analisi del pattern e non per la ricerca del tempo. GNU grep non è più veloce con -F, ad esempio (ha anche un bug che rende grep -Fpiù lento in locali multibyte - lo stesso modello costante con grepè in realtà significativamente più veloce!). D'altra parte BusyBox grep trae vantaggio molto da -Ffile di grandi dimensioni.
Gilles,

4
Forse dovrebbe essere menzionato che per schemi più complicati in cui l'alternanza deve essere solo per una parte dell'espressione regolare, può essere raggruppato con "\ (" e "\)" (l'escaping è per impostazione predefinita "espressioni regolari di base" ) (?).
Peter Mortensen,

4
Si noti che egrepprecede grep -E. Non è specifico per GNU (di certo non ha nulla a che fare con Linux). In realtà, troverai comunque sistemi come Solaris in cui l'impostazione predefinita grepnon supporta ancora -E.
Stéphane Chazelas,

90
egrep "foo|bar" *.txt

o

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

citando selettivamente la pagina man di 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.

All'inizio non ho letto più, quindi non ho riconosciuto le sottili differenze:

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

Ho sempre usato egrep e inutilmente le parentesi, perché ho imparato dagli esempi. Ora ho imparato qualcosa di nuovo. :)


22

Come detto TC1, -Fsembra essere un'opzione utilizzabile:

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

$> patterns="foo
bar" 

$> grep -F "${patterns}" text
foo
bar

1
@poige Non sapevo dell'opzione $ 'foo \ nbar', non sono sicuro di come funzioni l'espansione qui, ho bisogno di cercare, ma grazie, è davvero utile.
Haridsv,

Bello! Questa opzione sembra anche farla funzionare molto più velocemente (poiché disabilita regex).
qwertzguy,

15

Innanzitutto, è necessario utilizzare le virgolette per caratteri speciali. In secondo luogo, anche così, grepnon capiranno direttamente l'alternanza; dovresti usare egrep, o ( grepsolo con GNU ) grep -E.

egrep 'foo|bar' *.txt

(Le parentesi non sono necessarie a meno che l'alternanza non faccia parte di una regex più grande.)


4
In realtà, grep -Eè più standard di egrep.
jw013,

8

Se non hai bisogno di espressioni regolari, è molto più veloce da usare fgrepo grep -Fcon più parametri -e, come questo:

fgrep -efoo -ebar *.txt

fgrep(in alternativa grep -F) è molto più veloce del grep normale perché cerca stringhe fisse anziché espressioni regolari.


4
Si prega di vedere anche i commenti su questa pagina che menzionano che fgrepè deprecato.
phk,

6

Puoi provare il comando seguente per ottenere il risultato:

egrep 'rose.*lotus|lotus.*rose' some_file

3

Un modo economico e allegro per sostenere più schemi:

$ echo "foo" > ewq ; echo "bar" >> ewq ; grep -H -f ewq *.txt ; rm ewq

Potrebbe trarre vantaggio da una spiegazione.
Peter Mortensen,

2
La spiegazione è che l' -fopzione grep accetta un file con più pattern. Invece di creare un file temporaneo (che potresti dimenticare di eliminare in seguito), usa semplicemente la sostituzione del processo della shell:grep -f <(echo foo; echo bar) *.txt
Jakob

3

Pipe ( |) è un carattere shell speciale, quindi deve essere escape ( \|) o quotato come da manual ( man bash):

La citazione viene utilizzata per rimuovere il significato speciale di determinati caratteri o parole dalla shell. Può essere utilizzato per disabilitare il trattamento speciale per caratteri speciali, per impedire che le parole riservate vengano riconosciute come tali e per impedire l'espansione dei parametri.

Racchiudere i caratteri tra virgolette doppie conserva il valore letterale di tutti i caratteri tra virgolette

Una barra rovesciata non quotata ( \) è il carattere di escape.

Vedi: Quali personaggi devono essere evasi in Bash?

Ecco alcuni esempi (utilizzando strumenti non ancora menzionati):

  • Utilizzando ripgrep:

    • rg "foo|bar" *.txt
    • rg -e foo -e bar *.txt
  • Utilizzando git grep:

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

      Nota: supporta anche espressioni booleane come --and, --ore --not.

Per l'operazione AND per riga, vedere: Come eseguire grep con più pattern AND?

Per l'operazione AND per file, vedere: Come verificare l'esistenza di più stringhe o regex in un file?


3

Avevo registri di accesso in cui le date erano stupidamente formattate: [30 / giu / 2013: 08: 00: 45 +0200]

Ma dovevo visualizzarlo come: 30 / Jun / 2013 08:00:45

Il problema è che usando "OR" nella mia dichiarazione grep, stavo ricevendo le due espressioni di corrispondenza su due righe separate.

Ecco la soluzione:

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

2

TL; DR: se vuoi fare più cose dopo aver abbinato uno dei molteplici pattern, racchiudili come in \(pattern1\|pattern2\)

esempio: voglio trovare tutti i luoghi in cui una variabile che contiene il nome 'data' è definita come una stringa o int. (ad es. "int cronDate =" o "String textFormattedDateStamp ="):

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

Con grep -E, non è necessario sfuggire alle parentesi o al tubo, ovverogrep -E '(int|String) [a-zA-Z_]*date[a-zA-Z_]* ='


1

Questo funziona per me

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#

1

Esistono diversi modi per farlo.

  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'

La terza e la quarta opzione verranno visualizzate solo nei file ed eviteranno che le directory abbiano i .txtloro nomi.
Quindi, come per il tuo caso d'uso, puoi utilizzare una delle opzioni sopra menzionate.
Grazie!!


0

da aggiungere alla risposta di @ geekosaur , se si hanno più motivi che contengono anche schede e spazio, utilizzare il comando seguente

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

dove [[:blank:]]è la classe di caratteri RE che rappresenta uno spazio o un carattere di tabulazione

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.