web-dev-qa-db-fra.com

Comment puis-je rechercher des lignes contenant l'un des deux mots, mais pas les deux?

J'essaie d'utiliser grep pour afficher uniquement les lignes contenant l'un des deux mots, si un seul d'entre eux apparaît dans la ligne, mais pas s'ils sont sur la même ligne.

Jusqu'à présent, j'ai essayé grep pattern1 | grep pattern2 | ... mais n'a pas obtenu le résultat que j'attendais.

25
Trasmos

Un outil autre que grep est le chemin à parcourir.

En utilisant Perl, par exemple, la commande serait:

Perl -ne 'print if /pattern1/ xor /pattern2/'

Perl -ne exécute la commande donnée sur chaque ligne de stdin, qui dans ce cas imprime la ligne si elle correspond à /pattern1/ xor /pattern2/, ou en d'autres termes correspond à un modèle mais pas à l'autre (exclusif ou).

Cela fonctionne pour le modèle dans l'un ou l'autre ordre, et devrait avoir de meilleures performances que plusieurs appels de grep, et est également moins de frappe.

Ou, encore plus court, avec awk:

awk 'xor(/pattern1/,/pattern2/)'

ou pour les versions d'awk qui n'ont pas xor:

awk '/pattern1/+/pattern2/==1`
58
Chris

Avec GNU grep, vous pouvez passer les deux mots à grep puis supprimer les lignes contenant les deux motifs.

$ cat testfile.txt
abc
def
abc def
abc 123 def
1234
5678
1234 def abc
def abc

$ grep -w -e 'abc' -e 'def' testfile.txt | grep -v -e 'abc.*def' -e 'def.*abc'
abc
def
30
Haxiel

Essayez avec egrep

egrep  'pattern1|pattern2' file | grep -v -e 'pattern1.*pattern2' -e 'pattern2.*pattern1'
17
msp9011

Avec grep implémentations qui prennent en charge les expressions régulières de type Perl (comme pcregrep ou GNU ou ast-open grep -P), vous pouvez le faire en une seule grep invocation avec:

grep -P '^(?=.*pat1)(?!.*pat2)|^(?=.*pat2)(?!.*pat1)'

C'est-à-dire trouver les lignes qui correspondent à pat1 mais non pat2, ou pat2 mais non pat1.

(?=...) et (?!...) sont respectivement des opérateurs d'anticipation et d'anticipation négative. Donc, techniquement, ce qui précède cherche le début du sujet (^) à condition qu'il soit suivi de .*pat1 et non suivi de .*pat2, ou la même chose avec pat1 et pat2 inversé.

Ce n'est pas optimal pour les lignes qui contiennent les deux modèles car ils seraient ensuite recherchés deux fois. Vous pouvez à la place utiliser des opérateurs Perl plus avancés comme:

grep -P '^(?=.*pat1|())(?(1)(?=.*pat2)|(?!.*pat2))'

(?(1)yespattern|nopattern) correspond à yespattern si le 1st groupe de capture (vide () ci-dessus) correspond et nopattern sinon. Si ce () correspond, cela signifie pat1 ne correspond pas, nous recherchons donc pat2 (regard positif vers l'avenir), et nous recherchons paspat2 sinon (regard négatif à venir).

Avec sed, vous pouvez l'écrire:

sed -ne '/pat1/{/pat2/!p;d;}' -e '/pat2/p'
12
Stéphane Chazelas

En termes booléens, vous recherchez A xor B, qui peut s'écrire

(A et non B)

ou

(B et non A)

Étant donné que votre question ne mentionne pas que vous vous souciez de l'ordre de sortie tant que les lignes correspondantes sont affichées, l'expansion booléenne de A xor B est assez simple en grep:

$ cat << EOF > foo
> a b
> a
> b
> c a
> c b
> b a
> b c
> EOF
$ grep -w 'a' foo | grep -vw 'b'; grep -w 'b' foo | grep -vw 'a';
a
c a
b
c b
b c
3
Jim L.