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


11

Je veux rechercher des lignes avec 'word1' XOR 'word2' dans un fichier texte. Il devrait donc produire des lignes avec word1, word2 mais pas les lignes avec ces deux mots. Je voulais utiliser le XOR mais je ne sais pas comment l'écrire en 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 bien d'autres, mais n'a pas pu réussir.

Réponses:


6

grep 'word1\|word2' text.txtrecherche les lignes contenant word1ou word2. Cela inclut les lignes qui contiennent les deux.

grep word1 text.txt | grep word2recherche les lignes contenant word1et word2. Les deux mots peuvent se chevaucher (par exemple foobarcontient fooet ob). Une autre façon de rechercher des lignes contenant les deux mots, mais uniquement de manière non superposée, est de les rechercher dans l'un ou l'autre ordre:grep 'word1.*word2\|word2.*word1' text.txt

grep word1 text.txt | grep -v word2recherche les lignes contenant word1mais pas word2. L' -voption indique à grep de conserver les lignes non correspondantes et de supprimer les lignes correspondantes, au lieu de l'inverse. Cela vous donne la moitié des résultats souhaités. 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

Vous pouvez également commencer à partir des lignes contenant l'un ou l'autre des mots et supprimer les lignes contenant les deux mots. Étant donné les blocs de construction ci-dessus, c'est facile si les mots ne se chevauchent pas.

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

Merci c'est exactement ce que je cherchais. Les autres réponses sont également très intéressantes, alors regardez-les mal. Merci à tous pour votre contribution.
Lukali

17

Avec GNU awk:

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

Ou portable:

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

Avec un grepsupport pour -P(PCRE):

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

Avec sed:

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

Si vous voulez considérer des mots entiers seulement (qu'il n'y a ni , fooni bardans foobarou barbarpar exemple), vous aurez besoin de décider comment ces mots sont délimités. Si c'est par n'importe quel caractère autre que des lettres, des chiffres et des traits de soulignement comme le fait l' -woption de nombreuses grepimplémentations, alors vous les changeriez en:

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

Car sedcela devient un peu compliqué à moins que vous n'ayez une sedimplémentation comme GNU sed qui supporte \</ \>comme les limites de mots comme GNU awk.


6
Stéphane, écris un livre sur les scripts shell!
pfnuesel

Désolé, je n'ai commencé la ligne de commande qu'il y a quelques semaines. Comment pourrais-je le forcer à rechercher uniquement des mots? J'ai essayé -Pw et -wP mais cela m'a donné la mauvaise sortie. J'ai également essayé d'utiliser '' entre * mot1 / * mot2 et autour de mot1 / mot2.
Lukali

@Lukali, voir modifier.
Stéphane Chazelas

2

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.