web-dev-qa-db-fra.com

Comment nier un mot spécifique dans regex?

Je sais que je peux nier un groupe de caractères comme dans [^bar] mais j'ai besoin d'une expression régulière dans laquelle la négation s'applique à un mot spécifique. Ainsi, dans mon exemple, comment nier un "bar" réel et non "any chars in bar"?

565
Bostone

Une excellente façon de faire est d’utiliser lookahead négatif :

^(?!.*bar).*$

La construction de lookahead négative est la paire de parenthèses, avec la parenthèse ouvrante suivie d'un point d'interrogation et d'un point d'exclamation. À l'intérieur du lookahead [se trouve tout motif de regex].

625
Chris Van Opstal

À moins que les performances ne soient au cœur des préoccupations, il est souvent plus simple de faire passer vos résultats dans un deuxième passage, en ignorant ceux qui correspondent aux mots que vous souhaitez nier.

Les expressions régulières signifient généralement que vous utilisez un script ou une tâche à faible performance. Recherchez donc une solution facile à lire, à comprendre et à gérer.

59
Bryan Oakley

La regex suivante fera ce que vous voulez (dans la mesure où les regards négatifs et antérieurs sont supportés), en faisant correspondre les choses correctement; le seul problème est qu’il fait correspondre des caractères individuels (c’est-à-dire que chaque correspondance correspond à un seul caractère plutôt qu’à tous les caractères entre deux "barres" consécutives), ce qui peut entraîner un risque de surcharge supplémentaire si vous travaillez avec de très longues chaînes.

b(?!ar)|(?<!b)a|a(?!r)|(?<!ba)r|[^bar]
44
JAB

Vous pouvez soit utiliser un prospect négatif ou prospectif :

^(?!.*?bar).*
^(.(?<!bar))*?$

Ou utilisez simplement des bases:

^(?:[^b]+|b(?:$|[^a]|a(?:$|[^r])))*$

Celles-ci correspondent toutes à tout ce qui ne contient pas bar.

42
Gumbo

Je suis tombé sur ce fil de discussion en essayant d'identifier une expression rationnelle pour la déclaration suivante en anglais:

Pour une chaîne d'entrée, faites correspondre tout à moins que cette chaîne d'entrée soit exactement 'bar'; Par exemple, je veux faire correspondre "barrière" et "disbar" ainsi que "foo".

Voici la regex je suis venu avec

^(bar.+|(?!bar).*)$

Ma traduction anglaise de l'expression rationnelle est "faire correspondre la chaîne si elle commence par" bar "et si elle contient au moins un autre caractère, ou si la chaîne ne commence pas par" bar ".

30
ReQuest Programmer

Solution:

^(?!.*STRING1|.*STRING2|.*STRING3).*$

xxxxxx OK

xxxSTRING1xxx KO (est-il souhaité ou non)

xxxSTRING2xxx KO (est-il souhaité ou non)

xxxSTRING3xxx KO (est-il souhaité ou non)

28
sgrillon

La réponse acceptée est Nice, mais c’est vraiment une solution de rechange au manque d’un opérateur de négation de sous-expression simple dans les expressions rationnelles. C'est pourquoi grep --invert-match se ferme. Ainsi, dans * nixes, vous pouvez obtenir le résultat souhaité à l'aide de pipes et d'une seconde expression régulière.

grep 'something I want' | grep --invert-match 'but not these ones'

Encore une solution de contournement, mais peut-être plus facile à retenir.

9
Greg Bell

Je souhaite compléter la réponse acceptée et contribuer à la discussion avec ma réponse tardive.

@ChrisVanOpstal shared ce tutoriel sur les expressions rationnelles , qui est une excellente ressource pour apprendre les expressions rationnelles.

Cependant, la lecture a pris beaucoup de temps.

J'ai fait une feuille de triche pour la commodité mnémonique.

Cette référence est basée sur les accolades [], () et {} conduisant chaque classe et je trouve qu'il est facile de les rappeler.

Regex = {
 'single_character': ['[]', '.', {'negate':'^'}],
 'capturing_group' : ['()', '|', '\\', 'backreferences and named group'],
 'repetition'      : ['{}', '*', '+', '?', 'greedy v.s. lazy'],
 'anchor'          : ['^', '\b', '$'],
 'non_printable'   : ['\n', '\t', '\r', '\f', '\v'],
 'shorthand'       : ['\d', '\w', '\s'],
 }
3
Algebra

J'avais une liste de noms de fichiers et je voulais en exclure certains, avec ce genre de comportement (Ruby):

files = [
  'mydir/states.rb',      # don't match these
  'countries.rb',
  'mydir/states_bkp.rb',  # match these
  'mydir/city_states.rb' 
]
excluded = ['states', 'countries']

# set my_rgx here

result = WankyAPI.filter(files, my_rgx)  # I didn't write WankyAPI...
assert result == ['mydir/city_states.rb', 'mydir/states_bkp.rb']

Voici ma solution:

excluded_rgx = excluded.map{|e| e+'\.'}.join('|')
my_rgx = /(^|\/)((?!#{excluded_rgx})[^\.\/]*)\.rb$/

Mes hypothèses pour cette application:

  • La chaîne à exclure se trouve au début de l'entrée ou immédiatement après une barre oblique.
  • Les chaînes autorisées se terminent par .rb.
  • Les noms de fichiers autorisés n'ont pas de caractère . avant le .rb.
1

Je viens de penser à quelque chose d'autre qui pourrait être fait. C'est très différent de ma première réponse, comme il n'utilise pas d'expressions régulières, j'ai donc décidé de faire une deuxième réponse.

Utilisez l'équivalent de méthode split() de la langue de votre choix sur la chaîne avec le mot à nier en tant qu'argument sur lequel diviser. Un exemple utilisant Python:

>>> text = 'barbarasdbarbar 1234egb ar bar32 sdfbaraadf'
>>> text.split('bar')
['', '', 'asd', '', ' 1234egb ar ', '32 sdf', 'aadf']

La bonne chose à propos de le faire de cette façon, dans Python au moins (je ne me souviens pas si la fonctionnalité serait la même dans, par exemple, Visual Basic ou Java), c’est qu’elle vous permet de savoir indirectement quand "bar" a été répété dans la chaîne en raison du fait que les chaînes vides entre "bar" sont incluses dans la liste des résultats (bien que la chaîne vide au début soit due au fait qu'il y a un "bar" au début de la chaîne). Si vous ne le souhaitez pas, vous pouvez simplement supprimer les chaînes vides de la liste.

1
JAB

Extrait de ce commentaire par bkDJ :

^(?!bar$).*

La propriété de Nice de cette solution est qu'il est possible de clairement exclure (exclure) plusieurs mots:

^(?!bar$|foo$|banana$).*
0
leventov