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.
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`
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
Essayez avec egrep
egrep 'pattern1|pattern2' file | grep -v -e 'pattern1.*pattern2' -e 'pattern2.*pattern1'
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 1
st 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'
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