Jak używać grep do wyszukiwania linii z jednym z dwóch słów, ale nie z obydwoma?

11

Chcę wyszukać wiersze ze słowem „słowo 1” XOR „słowo 2” w pliku tekstowym. Powinien więc wypisywać wiersze ze słowem1, słowem2, ale nie wiersze z tymi dwoma słowami. Chciałem użyć XOR, ale nie wiem, jak napisać to w wierszu poleceń systemu Linux.

Próbowałem:

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

i wiele innych, ale nie udało się.

Lukali
źródło

Odpowiedzi:

6

grep 'word1\|word2' text.txtwyszukuje wiersze zawierające word1lub word2. Obejmuje to wiersze zawierające oba te elementy.

grep word1 text.txt | grep word2wyszukuje wiersze zawierające word1i word2. Te dwa słowa mogą się nakładać (np. foobarZawiera fooi ob). Innym sposobem wyszukiwania wierszy zawierających oba słowa, ale tylko w sposób nienakładający się, jest wyszukiwanie ich w dowolnej kolejności:grep 'word1.*word2\|word2.*word1' text.txt

grep word1 text.txt | grep -v word2wyszukuje wiersze zawierające, word1ale nie word2. Ta -vopcja mówi grepowi, aby zachowywał niepasujące linie i usuwał pasujące linie zamiast odwrotnie. To daje połowę pożądanych rezultatów. Dodając wyszukiwanie symetryczne, otrzymujesz wszystkie wiersze zawierające dokładnie jedno ze słów.

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

Alternatywnie możesz zacząć od linii zawierających jedno ze słów i usunąć linie zawierające oba słowa. Biorąc pod uwagę powyższe elementy, jest to łatwe, jeśli słowa się nie nakładają.

grep 'word1\|word2' text.txt | grep -v 'word1.*word2\|word2.*word1'
Gilles „SO- przestań być zły”
źródło
Dziękuję, właśnie tego szukałem. Inne odpowiedzi są również bardzo interesujące, więc źle je przeglądam. Dziękujemy wszystkim za wkład.
Lukali,
17

Z GNU awk:

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

Lub przenośnie:

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

Dzięki grepwsparciu dla -P(PCRE):

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

Z sed:

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

Jeśli chcesz wziąć pod uwagę tylko całe słowa (które nie jest ani fooani barw foobarlub barbarna przykład), to musisz zdecydować, w jaki sposób te słowa są ograniczone. Jeśli jest to jakikolwiek znak inny niż litery, cyfry i podkreślniki, jak w -wprzypadku wielu grepimplementacji, możesz zmienić je na:

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

To sedstaje się nieco skomplikowane, chyba że masz sedimplementację taką jak GNU, sed która obsługuje \</ \>jak granice słów jak GNU awk.

Stéphane Chazelas
źródło
6
Stephane, napisz książkę o skryptach powłoki!
pfnuesel
Przepraszam, że uruchomiłem wiersz poleceń dopiero kilka tygodni temu. Jak zmusić go do wyszukiwania tylko słów? Próbowałem -Pw i -wP, ale to dało mi zły wynik. Próbowałem także użyć „” między * słowem1 / * słowem2 a wokół słowa1 / słowa2.
Lukali
@Lukali, patrz edycja.
Stéphane Chazelas
2

Rozwiązanie bash:

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

Aby to przetestować:

$ ./script {foo,bar}\ {foo,bar} neither
foo foo
bar bar
Izaak
źródło