234 votes

Expressions régulières et négation d'un groupe de caractères entier

Je suis en train de tenter quelque chose qui devrait être assez évident pour moi, mais qui ne l'est pas. J'essaie de faire correspondre une chaîne qui ne contient PAS une séquence spécifique de caractères. J'ai essayé d'utiliser [^ab] , [^(ab)] etc. pour correspondre à des chaînes de caractères ne contenant aucun 'a' ou 'b', ou seulement des 'a' ou seulement des 'b' ou 'ba', mais ne correspondant pas à 'ab'. Les exemples que j'ai donnés ne correspondent pas à 'ab', c'est vrai, mais ils ne correspondent pas non plus à 'a' seul et j'en ai besoin. Existe-t-il un moyen simple d'y parvenir ?

0 votes

@finnw peut-être qu'il s'y référait dans le contexte de stackoverflow.com/q/36754105/3186555 ?

397voto

Peter Boughton Points 49510

En utilisant une classe de caractères telle que [^ab] correspondra à un caractère unique qui ne fait pas partie de l'ensemble des caractères. (Avec le ^ étant la partie négative).

Pour faire correspondre une chaîne qui ne contient pas la séquence de plusieurs caractères ab vous voulez utiliser un lookahead négatif :

^(?:(?!ab).)+$

Et l'expression ci-dessus détectée en mode commentaire de regex est :

(?x)    # enable regex comment mode
^       # match start of line/string
(?:     # begin non-capturing group
  (?!   # begin negative lookahead
    ab  # literal text sequence ab
  )     # end negative lookahead
  .     # any single character
)       # end non-capturing group
+       # repeat previous match one or more times
$       # match end of line/string

38 votes

Disséquer le regex m'a été très utile. Merci.

0 votes

Et pour le remplacer, probablement juste ^((?!ab).+)$ .

1 votes

Une petite note. Le site . de "tout caractère unique" ne concerne que la même ligne. Si vous avez besoin de faire cela pour des regex multi-lignes, vous devrez peut-être le remplacer par (.|\n)

216voto

Alan Moore Points 39365

Utilisez un lookahead négatif :

^(?!.*ab).*$

UPDATE : Dans les commentaires ci-dessous, j'ai indiqué que cette approche est plus lente que celle donnée dans La réponse de Pierre . J'ai effectué quelques tests depuis, et j'ai constaté que c'est vraiment un peu plus rapide. Cependant, la raison de préférer cette technique à l'autre n'est pas la vitesse, mais la simplicité.

L'autre technique, décrite ici en tant que trempé gourmand jeton est adapté à des problèmes plus complexes, comme la correspondance de textes délimités où les délimiteurs sont constitués de plusieurs caractères (comme le HTML, comme Luke l'a commenté). en dessous de ). Pour le problème décrit dans la question, c'est exagéré.

Pour ceux que ça intéresse, j'ai testé avec un gros morceau de texte Lorem Ipsum, en comptant le nombre de lignes qui ne contiennent pas le mot "quo". Voici les regex que j'ai utilisées :

(?m)^(?!.*\bquo\b).+$

(?m)^(?:(?!\bquo\b).)+$

Que je recherche des correspondances dans l'ensemble du texte ou que je le divise en lignes et que je les fasse correspondre individuellement, le lookahead ancré est systématiquement plus performant que le lookahead flottant.

14 votes

Je crois que ceci est plus efficace : (? :(?!ab).)*

1 votes

Il souhaite également utiliser des marqueurs de début et de fin pour appliquer la vérification à l'ensemble de la chaîne.

8 votes

@Blixit : oui, c'est vrai. Mais c'est aussi plus difficile à lire, surtout pour les débutants en regex. Celle que j'ai postée sera suffisamment efficace pour la plupart des applications.

64voto

abhinav Points 1736

Oui, c'est ce qu'on appelle le negative lookahead. C'est comme ça . (?!regex here) . Donc abc(?!def) correspondra à abc no suivi de def. Donc ça correspondra à abce, abc, abck, etc.

De même, il existe un lookahead positif - (?=regex here) . Donc abc(?=def) correspondra à abc suivi de def.

Il y a aussi des regards négatifs et positifs. (?<!regex here) y (?<=regex here) respectivement

Un point à noter est que le lookahead négatif est de largeur nulle. C'est-à-dire qu'il n'est pas considéré comme ayant pris de la place.

Cela peut donc ressembler à a(?=b)c correspondra à "abc" mais ne le fera pas. Il fera correspondre 'a', puis le lookahead positif avec 'b' mais il n'avancera pas dans la chaîne. Ensuite, il essaiera de faire correspondre le 'c' avec le 'b', ce qui ne fonctionnera pas. De même, ^a(?=b)b$ correspondra à 'ab' et non à 'abb', car la largeur de l'enveloppe est nulle (dans la plupart des implémentations de regex).

Plus d'informations sur este page

2 votes

La référence aux opérateurs "lookbehind" était également utile, car tous les analyseurs de regex en ligne et la documentation ne l'incluent pas, même si elle est valide et fonctionne.

5voto

Copas Points 3729

L'utilisation d'une regex comme vous l'avez décrit est la méthode la plus simple (pour autant que je sache). Si vous voulez un intervalle, vous pouvez utiliser [^a-f].

4voto

user71268 Points 370

Le moyen le plus simple est de retirer entièrement la négation de l'expression régulière :

if (!userName.matches("^([Ss]ys)?admin$")) { ... }

0 votes

Bien que cela soit utile si vous consommez juste cette expression, dans le cadre d'une expression plus large la méthode de lookahead négatif décrite par Peter permet d'utiliser des conditions positives et négatives dans une seule chaîne.

0 votes

Tout à fait vrai. Mais la question était de "faire correspondre une chaîne qui ne contient PAS une séquence spécifique de caractères". Je pense que dans ce cas, le lookahead négatif est excessif.

2 votes

Vous ne pouvez pas faire ça si vous utilisez un éditeur de texte.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X