Come usare grep per cercare una riga con una delle due parole ma non entrambe?


11

Voglio cercare le righe con 'word1' XOR 'word2' in un file di testo. Quindi dovrebbe produrre righe con word1, word2 ma non le righe con entrambe queste parole. Volevo usare XOR ma non so come scriverlo nella riga di comando di Linux.

Provai:

grep 'word1\|word2' text.txt
grep word1 word2 text.txt
grep word1 text.txt | grep word2
grep 'word1\^word2' text.txt

e molti altri, ma non sono riuscito ad avere successo.

Risposte:


6

grep 'word1\|word2' text.txtcerca le righe contenenti word1o word2. Ciò include le righe che contengono entrambi.

grep word1 text.txt | grep word2cerca le righe che contengono word1e word2. Le due parole possono sovrapporsi (ad esempio foobarcontiene fooe ob). Un altro modo per cercare le righe che contengono entrambe le parole, ma solo in modo non sovrapposto, è cercarle in entrambi gli ordini:grep 'word1.*word2\|word2.*word1' text.txt

grep word1 text.txt | grep -v word2cerca le righe che contengono word1ma non word2. L' -vopzione dice a grep di mantenere le linee non corrispondenti e di rimuovere le linee corrispondenti, anziché il contrario. Questo ti dà la metà dei risultati desiderati. Aggiungendo la ricerca simmetrica, si ottengono tutte le righe contenenti esattamente una delle parole.

grep word1 text.txt | grep -v word2
grep word2 text.txt | grep -v word1

In alternativa, puoi iniziare dalle righe contenenti entrambe le parole e rimuovere le righe contenenti entrambe le parole. Dati i blocchi di cui sopra, questo è facile se le parole non si sovrappongono.

grep 'word1\|word2' text.txt | grep -v 'word1.*word2\|word2.*word1'

Grazie, questo è esattamente quello che stavo cercando. Le altre risposte sono anche molto interessanti, quindi guardiamole male. Grazie a tutti per aver contribuito.
Lukali,

17

Con GNU awk:

$ printf '%s\n' {foo,bar}{bar,foo} neither | gawk 'xor(/foo/,/bar/)'
foofoo
barbar

O portabilmente:

awk '((/foo/) + (/bar/)) % 2'

Con un grepsupporto per -P(PCRE):

grep -P '^((?=.*foo)(?!.*bar)|(?=.*bar)(?!.*foo))'

Con sed:

sed '
  /foo/{
    /bar/d
    b
  }
  /bar/!d'

Se vuoi considerare solo parole intere (che non esistono foobarin foobarbarbarper esempio), dovrai decidere come delimitarle. Se è composto da caratteri diversi da lettere, cifre e caratteri di sottolineatura come l' -wopzione di molte grepimplementazioni, allora cambieresti in:

gawk 'xor(/\<foo\>/,/\<bar\>/)'
awk '((/(^|[^[:alnum:]_)foo([^[:alnum:]_]|$)/) + \
      (/(^|[^[:alnum:]_)bar([^[:alnum:]_]|$)/)) % 2'
grep -P '^((?=.*\bfoo\b)(?!.*\bbar\b)|(?=.*\bbar\b)(?!.*\bfoo\b))'

Per sedquesto diventa un po 'complicato a meno che tu non abbia sedun'implementazione come GNU sed che supporti \</ \>come confini di parole come GNU awk.


6
Stephane, per favore, scrivi un libro sulla shell scripting!
pfnuesel,

Mi spiace di aver avviato la riga di comando solo poche settimane fa. Come lo forzerei a cercare solo parole? Ho provato -Pw e -wP ma questo mi ha dato un output sbagliato. Ho anche provato a usare '' tra * word1 / * word2 e intorno a word1 / word2.
Lukali,

@Lukali, vedi modifica.
Stéphane Chazelas,

2

Una soluzione bash:

#!/bin/bash 
while (( $# )); do
    a=0 ; [[ $1 =~ foo ]] && a=1 
    b=0 ; [[ $1 =~ bar ]] && b=1
    (( a ^ b )) && echo "$1"
    shift
done

Per testarlo:

$ ./script {foo,bar}\ {foo,bar} neither
foo foo
bar bar
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.