Puisque personne d'autre n'a donné de réponse directe à la question qui a été posée , je vais le faire.
La réponse est qu'avec POSIX grep
, il est impossible de satisfaire littéralement cette demande:
grep "<Regex for 'doesn't contain hede'>" input
La raison en est que POSIX grep
n'est requis que pour travailler avec les expressions régulières de base , qui ne sont tout simplement pas assez puissantes pour accomplir cette tâche (elles ne sont pas capables d'analyser les langues normales, en raison du manque d'alternance et de parenthèses).
Cependant, GNU grep
implémente des extensions qui le permettent. En particulier, \|
est l'opérateur d'alternance dans la mise en œuvre de BRE par GNU, et \(
et \)
sont les parenthèses. Si votre moteur d'expression régulière prend en charge l'alternance, les expressions de parenthèses négatives, les parenthèses et l'étoile Kleene, et est capable d'ancrer au début et à la fin de la chaîne, c'est tout ce dont vous avez besoin pour cette approche. Notez cependant que les jeux négatifs [^ ... ]
sont très pratiques en plus de ceux-ci, car sinon, vous devez les remplacer par une expression de la forme (a|b|c| ... )
qui répertorie tous les caractères qui ne sont pas dans le jeu, ce qui est extrêmement fastidieux et trop long, d'autant plus si l'ensemble des caractères est Unicode.
Avec GNU grep
, la réponse serait quelque chose comme:
grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" input
(trouvé avec Graal et quelques optimisations supplémentaires faites à la main).
Vous pouvez également utiliser un outil qui implémente les expressions régulières étendues , comme egrep
pour supprimer les barres obliques inverses:
egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input
Voici un script pour le tester (notez qu'il génère un fichier testinput.txt
dans le répertoire courant):
#!/bin/bash
REGEX="^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$"
# First four lines as in OP's testcase.
cat > testinput.txt <<EOF
hoho
hihi
haha
hede
h
he
ah
head
ahead
ahed
aheda
ahede
hhede
hehede
hedhede
hehehehehehedehehe
hedecidedthat
EOF
diff -s -u <(grep -v hede testinput.txt) <(grep "$REGEX" testinput.txt)
Dans mon système, il imprime:
Files /dev/fd/63 and /dev/fd/62 are identical
comme prévu.
Pour ceux qui s'intéressent aux détails, la technique utilisée consiste à convertir l'expression régulière correspondant au mot en un automate fini, puis à inverser l'automate en changeant chaque état d'acceptation en non-acceptation et vice versa, puis en reconvertissant l'AF résultante en une expression régulière.
Enfin, comme tout le monde l'a noté, si votre moteur d'expression régulière prend en charge l'anticipation négative, cela simplifie beaucoup la tâche. Par exemple, avec GNU grep:
grep -P '^((?!hede).)*$' input
Mise à jour: J'ai récemment trouvé l'excellente bibliothèque FormalTheory de Kendall Hopkins , écrite en PHP, qui fournit une fonctionnalité similaire à Grail. En l'utilisant, et un simplificateur écrit par moi-même, j'ai pu écrire un générateur en ligne d'expressions régulières négatives avec une phrase d'entrée (seuls les caractères alphanumériques et spatiaux sont actuellement pris en charge): http://www.formauri.es/personal/ pgimeno / misc / non-match-regex /
Car hede
il délivre:
^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$
qui est équivalent à ce qui précède.
([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*
:? L'idée est simple. Gardez la correspondance jusqu'à ce que vous voyiez le début de la chaîne indésirable, puis ne correspondez que dans les N-1 cas où la chaîne n'est pas terminée (où N est la longueur de la chaîne). Ces cas N-1 sont "h suivi de non-e", "il a suivi de non-d" et "hed suivi de non-e". Si vous avez réussi à passer ces N-1 cas, avec succès ne pas correspondre à la chaîne non désirée afin que vous puissiez commencer à chercher à[^h]*
nouveau