web-dev-qa-db-fra.com

Comment utiliser Grep pour rechercher une ligne avec l'un des deux mots mais pas les deux?

Je souhaite rechercher des lignes avec 'Word1' XOR 'Word2' dans un fichier texte. Donc, il devrait produire des lignes avec Word1, Word2 mais pas les lignes avec ces deux mots. Je voulais Utilisez le XOR mais je ne sais pas comment écrire cela dans la ligne de commande Linux.

J'ai essayé:

grep 'Word1\|Word2' text.txt
grep Word1 Word2 text.txt
grep Word1 text.txt | grep Word2
grep 'Word1\^Word2' text.txt

et beaucoup plus, mais ne pouvaient pas réussir.

11
Lukali

grep 'Word1\|Word2' text.txt Recherche des lignes contenant Word1 ou Word2. Cela inclut des lignes contenant les deux.

grep Word1 text.txt | grep Word2 Recherche des lignes contenant Word1 et Word2. Les deux mots peuvent se chevaucher (par ex. foobar contient foo et ob). Une autre façon de rechercher des lignes contenant à la fois des mots, mais uniquement de manière non chevauchante, est de les rechercher dans l'un ou l'autre ordre: grep 'Word1.*Word2\|Word2.*Word1' text.txt

grep Word1 text.txt | grep -v Word2 Recherche des lignes contenant Word1 mais non Word2. Les -v L'option indique à Grep de conserver des lignes non correspondantes et de supprimer les lignes correspondantes, au lieu de l'opposé. Cela vous donne la moitié des résultats que vous vouliez. En ajoutant la recherche symétrique, vous obtenez toutes les lignes contenant exactement l'un des mots.

grep Word1 text.txt | grep -v Word2
grep Word2 text.txt | grep -v Word1

Alternativement, vous pouvez commencer par les lignes contenant des mots et retirer les lignes contenant les deux mots. Compte tenu des blocs de construction ci-dessus, cela est facile si les mots ne se chevauchent pas.

grep 'Word1\|Word2' text.txt | grep -v 'Word1.*Word2\|Word2.*Word1'

Avec GNU awk:

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

Ou portablement:

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

Avec un grep avec support pour -P (PCRE):

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

Avec sed:

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

Si vous souhaitez considérer les mots entiers uniquement (qu'il n'y a ni foo ni bar in foobar ou barbar par exemple), vous devez décider. Comment ces mots sont délimités. Si c'est par n'importe quel caractère autre que les lettres, les chiffres et le soulignement comme le -w option de plusieurs grep _ La mise en œuvre, alors vous changeriez celles-ci pour:

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

Pour sed qui devient un peu compliqué à moins que vous ayez une implémentation sed comme GNU sed qui prend en charge \</\> comme limites de mots comme GNU awk fait.

17
Stéphane Chazelas

Une solution bash:

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

Pour le tester:

$ ./script {foo,bar}\ {foo,bar} neither
foo foo
bar bar
2
Isaac