Wie kann man mit grep nach einer Zeile mit einem von zwei Wörtern suchen, aber nicht mit beiden?


11

Ich möchte in einer Textdatei nach Zeilen mit 'word1' XOR 'word2' suchen. Es sollte also Zeilen mit Wort1, Wort2 ausgeben, aber nicht die Zeilen mit diesen beiden Wörtern. Ich wollte das XOR verwenden, weiß aber nicht, wie ich das in der Linux-Befehlszeile schreiben soll.

Ich habe es versucht:

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

und viele mehr, konnte aber keinen Erfolg bekommen.

Antworten:


6

grep 'word1\|word2' text.txtsucht nach Zeilen mit word1oder word2. Dies schließt Zeilen ein, die beide enthalten.

grep word1 text.txt | grep word2sucht nach Zeilen mit word1und word2. Die beiden Wörter können sich überschneiden (zB foobarenthält foound ob). Eine andere Möglichkeit, nach Zeilen zu suchen, die beide Wörter enthalten, jedoch nicht überlappend, besteht darin, sie in der folgenden Reihenfolge zu suchen:grep 'word1.*word2\|word2.*word1' text.txt

grep word1 text.txt | grep -v word2sucht nach Zeilen, die word1aber nicht enthalten word2. Die -vOption weist grep an, nicht übereinstimmende Zeilen beizubehalten und übereinstimmende Zeilen zu entfernen, anstatt das Gegenteil. Dies gibt Ihnen die Hälfte der gewünschten Ergebnisse. Durch Hinzufügen der symmetrischen Suche erhalten Sie alle Zeilen, die genau eines der Wörter enthalten.

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

Alternativ können Sie mit den Zeilen beginnen, die eines der beiden Wörter enthalten, und die Zeilen entfernen, die beide Wörter enthalten. Angesichts der obigen Bausteine ​​ist dies einfach, wenn sich die Wörter nicht überschneiden.

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

Danke, genau das habe ich gesucht. Die anderen Antworten sind ebenfalls sehr interessant, also schauen Sie sie sich nicht an. Vielen Dank an alle für ihren Beitrag.
Lukali

17

Mit GNU awk:

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

Oder tragbar:

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

Mit einem grepmit Unterstützung für -P(PCRE):

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

Mit sed:

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

Wenn Sie nur ganze Wörter berücksichtigen möchten (die es weder foonoch barin foobaroder barbarzum Beispiel gibt), müssen Sie entscheiden, wie diese Wörter abgegrenzt werden. Wenn es sich um ein anderes Zeichen als Buchstaben, Ziffern und Unterstriche handelt, wie dies -wbei vielen grepImplementierungen der Fall ist, ändern Sie diese in:

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

Dies sedwird etwas kompliziert, es sei denn, Sie haben eine sedImplementierung wie GNU sed , die \</ \>als Wortgrenzen wie GNU awkunterstützt.


6
Stephane, bitte schreibe ein Buch über Shell-Scripting!
Pfnuesel

Entschuldigung, ich habe erst vor ein paar Wochen mit der Kommandozeile begonnen. Wie würde ich es zwingen, nur nach Wörtern zu suchen? Ich habe -Pw und -wP ausprobiert, aber dies gab mir die falsche Ausgabe. Ich habe auch versucht, '' zwischen * word1 / * word2 und um word1 / word2 zu verwenden.
Lukali

@ Lukali, siehe bearbeiten.
Stéphane Chazelas

2

Eine Bash-Lösung:

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

Um es zu testen:

$ ./script {foo,bar}\ {foo,bar} neither
foo foo
bar bar
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.