Expresión regular para que coincida con una línea que no contiene una palabra

4295

Sé que es posible hacer coincidir una palabra y luego revertir las coincidencias utilizando otras herramientas (por ejemplo grep -v). Sin embargo, ¿es posible hacer coincidir líneas que no contengan una palabra específica, por ejemplo hede, usando una expresión regular?

Entrada:

hoho
hihi
haha
hede

Código:

grep "<Regex for 'doesn't contain hede'>" input

Salida deseada:

hoho
hihi
haha
knaser
fuente
85
Probablemente un par de años tarde, pero ¿qué tiene de malo ([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*:? La idea es simple. Siga haciendo coincidir hasta que vea el inicio de la cadena no deseada, luego solo haga coincidir en los casos N-1 donde la cadena está sin terminar (donde N es la longitud de la cadena). Estos casos N-1 son "h seguido de no e", "seguido de no d" y "hed seguido de no e". Si logró pasar estos casos N-1, no coincidió con la cadena no deseada para que pueda comenzar a buscar de [^h]*nuevo
stevendesu
323
@stevendesu: intente esto para 'una palabra muy larga' o incluso una media frase mejor. Diviértete escribiendo. Por cierto, es casi ilegible. No sé sobre el impacto en el rendimiento.
Peter Schuetze
13
@PeterSchuetze: Claro que no es bonito para palabras muy largas, pero es una solución viable y correcta. Aunque no he realizado pruebas en el rendimiento, no me imagino que sea demasiado lento ya que la mayoría de las últimas reglas se ignoran hasta que vea una h (o la primera letra de la palabra, oración, etc.). Y podría generar fácilmente la cadena de expresiones regulares para cadenas largas utilizando la concatenación iterativa. Si funciona y se puede generar rápidamente, ¿es importante la legibilidad? Para eso están los comentarios.
stevendesu
57
@stevendesu: estoy incluso más tarde, pero esa respuesta es casi completamente incorrecta. por un lado, requiere que el sujeto contenga "h", lo cual no debería ser necesario, dado que la tarea es "líneas de coincidencia que [no] contienen una palabra específica". supongamos que pretendía hacer que el grupo interno sea opcional, y que el patrón está anclado: ^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$ esto falla cuando las instancias de "hede" están precedidas por instancias parciales de "hede", como en "hhede".
jaytea
8
Esta pregunta se ha agregado a las Preguntas frecuentes sobre la expresión regular de desbordamiento de pila , en "Regex-Fu avanzado".
aliteralmind

Respuestas:

5895

La noción de que regex no admite la coincidencia inversa no es del todo cierto. Puede imitar este comportamiento utilizando miradas negativas:

^((?!hede).)*$

La expresión regular anterior coincidirá con cualquier cadena o línea sin un salto de línea, que no contenga la (sub) cadena 'hede'. Como se mencionó, esto no es algo que regex sea "bueno" (o debería hacer), pero aún así, es posible.

Y si necesita hacer coincidir también los caracteres de salto de línea, use el modificador DOT-ALL (el seguimiento sen el siguiente patrón):

/^((?!hede).)*$/s

o úsalo en línea:

/(?s)^((?!hede).)*$/

(donde /.../están los delimitadores de expresiones regulares, es decir, no forman parte del patrón)

Si el modificador DOT-ALL no está disponible, puede imitar el mismo comportamiento con la clase de caracteres [\s\S]:

/^((?!hede)[\s\S])*$/

Explicación

Una cadena es solo una lista de ncaracteres. Antes y después de cada personaje, hay una cadena vacía. Entonces, una lista de ncaracteres tendrá n+1cadenas vacías. Considere la cadena "ABhedeCD":

    ┌──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┐
S = e1 A e2 B e3 h e4 e e5 d e6 e e7 C e8 D e9
    └──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┘

index    0      1      2      3      4      5      6      7

donde los e's son las cadenas vacías. La expresión regular (?!hede).mira hacia adelante para ver si no se "hede"puede ver ninguna subcadena , y si ese es el caso (entonces se ve algo más), entonces el .(punto) coincidirá con cualquier carácter, excepto un salto de línea. Las miradas también se llaman aserciones de ancho cero porque no consumen ningún carácter. Solo afirman / validan algo.

Entonces, en mi ejemplo, cada cadena vacía se valida por primera vez para ver si no hay ninguna "hede"más adelante, antes de que el carácter .(punto) consuma un carácter . La expresión regular (?!hede).hará que sólo una vez, por lo que se envuelve en un grupo, y repetido cero o más veces: ((?!hede).)*. Finalmente, el inicio y el final de la entrada están anclados para garantizar que se consuma toda la entrada:^((?!hede).)*$

Como se puede ver, la entrada "ABhedeCD"fallará porque el e3, la expresión regular (?!hede)falla (no es "hede" más adelante!).

Bart Kiers
fuente
26
No iría tan lejos como para decir que esto es algo en lo que regex es malo. La conveniencia de esta solución es bastante obvia y el impacto en el rendimiento en comparación con una búsqueda programática a menudo no será importante.
Archimaredes
29
Estrictamente hablando, el loook-ahead negativo hace que la expresión regular no sea regular.
Peter K
55
@PeterK, claro, pero esto es SO, no MathOverflow o CS-Stackexchange. Las personas que hacen una pregunta aquí generalmente buscan una respuesta práctica. La mayoría de las bibliotecas o herramientas (como las grepque menciona el OP) con soporte de expresiones regulares tienen características que las hacen no regulares en un sentido teórico.
Bart Kiers
19
@Bart Kiers, no ofendas tu respuesta, solo este abuso de terminología me irrita un poco. La parte realmente confusa aquí es que las expresiones regulares en el sentido estricto pueden hacer mucho lo que OP quiere, pero el lenguaje común para escribirlas no lo permite, lo que conduce a soluciones (matemáticamente feas) como miradas. Vea esta respuesta a continuación y mi comentario allí para ver la forma adecuada (teóricamente alineada) de hacerlo. No hace falta decir que funciona más rápido en entradas grandes.
Peter K
17
En caso de que alguna vez se haya preguntado cómo hacer esto en vim:^\(\(hede\)\@!.\)*$
calvas
739

Tenga en cuenta que la solución a no comienza con "hede" :

^(?!hede).*$

es generalmente mucho más eficiente que la solución para que no contenga "hede" :

^((?!hede).)*$

El primero verifica "hede" solo en la primera posición de la cadena de entrada, en lugar de en cada posición.

FireCoding
fuente
55
Gracias, lo usé para validar que la cadena no contiene la secuencia de dígitos ^ ((?! \ D {5,}).) *
Samih A
2
¡Hola! No puedo componer no termina con la expresión regular "hede" . ¿Puedes ayudar con eso?
Aleks Ya
1
@AleksYa: simplemente use la versión "contener" e incluya el ancla final en la cadena de búsqueda: cambie la cadena a "no coincidente" de "hede" a "hede $"
Nyerguds
2
@AleksYa: la versión no termina podría hacerse utilizando negativo de búsqueda hacia atrás como: (.*)(?<!hede)$. La versión de @Nyerguds también funcionaría, pero pierde por completo el punto de rendimiento que menciona la respuesta.
thisismydesign
55
¿Por qué dicen tantas respuestas ^((?!hede).)*$? ¿No es más eficiente de usar ^(?!.*hede).*$? Hace lo mismo pero en menos pasos
JackPRead
208

Si solo lo está usando para grep, puede usarlo grep -v hedepara obtener todas las líneas que no contienen hede.

ETA Oh, releyendo la pregunta, grep -ves probablemente lo que quisiste decir con "opciones de herramientas".

Atenea
fuente
22
Consejo: para filtrar progresivamente lo que no desea: grep -v "hede" | grep -v "hihi" | ... etc.
Olivier Lalonde
51
O usando solo un procesogrep -v -e hede -e hihi -e ...
Olaf Dietsche
15
O simplemente grep -v "hede\|hihi":)
Putnik
2
Si tiene muchos patrones que desea filtrar, grep -vf pattern_file file
póngalos
44
O simplemente egrepo grep -Ev "hede|hihi|etc"para evitar el escape incómodo.
Amit Naidu
161

Responder:

^((?!hede).)*$

Explicación:

^al comienzo de la cadena, (agrupe y capture a \ 1 (0 o más veces (que coincida con la mayor cantidad posible)),
(?!mire hacia adelante para ver si no hay,

hede tu cuerda

)fin de la anticipación, .cualquier carácter excepto \ n,
)*fin de \ 1 (Nota: debido a que está utilizando un cuantificador en esta captura, solo la ÚLTIMA repetición del patrón capturado se almacenará en \ 1)
$antes de un \ n opcional, y el final de la cuerda

Jessica
fuente
14
increíble que funcionó para mí en sublime texto 2 usando múltiples palabras ' ^((?!DSAU_PW8882WEB2|DSAU_PW8884WEB2|DSAU_PW8884WEB).)*$'
Damodar Bashyal
3
@DamodarBashyal Sé que llego bastante tarde aquí, pero podría eliminar por completo el segundo término allí y obtendría exactamente los mismos resultados
forresthopkinsa
99

Las respuestas dadas están perfectamente bien, solo un punto académico:

Expresiones regulares en el significado de las ciencias de la computación teórica NO SON CAPAZ de hacerlo así. Para ellos tenía que verse así:

^([^h].*$)|(h([^e].*$|$))|(he([^h].*$|$))|(heh([^e].*$|$))|(hehe.+$) 

Esto solo hace una coincidencia COMPLETA. Hacerlo para sub-partidos sería incluso más incómodo.

Hades32
fuente
1
Es importante tener en cuenta que esto solo usa expresiones regulares POSIX.2 básicas y, por lo tanto, mientras que Terse es más portátil para cuando PCRE no está disponible.
Steve-o
55
Estoy de acuerdo. Muchas, si no la mayoría, las expresiones regulares no son lenguajes regulares y no pueden ser reconocidos por un autómata finito.
ThomasMcLeod
@ThomasMcLeod, Hades32: ¿Está dentro de los reinos de cualquier lenguaje regular posible poder decir ' no ' y ' y ' así como el ' o ' de una expresión como ' (hede|Hihi)'? (Esta quizás sea una pregunta para CS.)
James Haigh
77
@JohnAllen: ¡YO! ... Bueno, no la expresión regular real sino la referencia académica, que también se relaciona estrechamente con la complejidad computacional; Los PCRE fundamentalmente no pueden garantizar la misma eficiencia que las expresiones regulares POSIX.
James Haigh
44
Lo siento, esta respuesta simplemente no funciona, coincidirá con él e incluso coincidirá parcialmente con él (la segunda mitad)
Falco
60

Si desea que la prueba de expresión regular solo falle si la cadena completa coincide, lo siguiente funcionará:

^(?!hede$).*

por ejemplo: si desea permitir todos los valores excepto "foo" (es decir, "foofoo", "barfoo" y "foobar" pasarán, pero "foo" fallará), use: ^(?!foo$).*

Por supuesto, si está verificando la igualdad exacta , una mejor solución general en este caso es verificar la igualdad de la cadena, es decir

myStr !== 'foo'

Incluso podría poner la negación fuera de la prueba si necesita características de expresiones regulares (aquí, insensibilidad a mayúsculas y minúsculas):

!/^[a-f]oo$/i.test(myStr)

Sin embargo, la solución de expresiones regulares en la parte superior de esta respuesta puede ser útil en situaciones en las que se requiere una prueba de expresión regular positiva (quizás por una API).

Roy Tinker
fuente
¿Qué pasa con los espacios en blanco finales? Por ejemplo, si quiero que la prueba falle con la cadena " hede "?
Eagor
@eagor la \sdirectiva coincide con un solo personaje de espacio en blanco
Roy Tinker
gracias, pero no pude actualizar la expresión regular para que esto funcione.
Eagor
2
@eagor:^(?!\s*hede\s*$).*
Roy Tinker
52

FWIW, dado que los lenguajes regulares (también conocidos como lenguajes racionales) están cerrados bajo complementación, siempre es posible encontrar una expresión regular (también conocida como expresión racional) que niega otra expresión. Pero no muchas herramientas implementan esto.

Vcsn admite este operador (que denota {c}, postfix).

En primer lugar, definir el tipo de sus expresiones: las etiquetas son letra ( lal_char) para elegir aa zpor ejemplo (que define el alfabeto cuando se trabaja con la complementación es, por supuesto, muy importante), y el "valor" calculado para cada palabra es sólo un booleano : truela palabra es aceptada false, rechazada.

En Python:

In [5]: import vcsn
        c = vcsn.context('lal_char(a-z), b')
        c
Out[5]: {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}  𝔹

entonces ingresas tu expresión:

In [6]: e = c.expression('(hede){c}'); e
Out[6]: (hede)^c

convierte esta expresión a un autómata:

In [7]: a = e.automaton(); a

El autómata correspondiente

finalmente, convierta este autómata a una expresión simple.

In [8]: print(a.expression())
        \e+h(\e+e(\e+d))+([^h]+h([^e]+e([^d]+d([^e]+e[^]))))[^]*

donde +generalmente se denota |, \edenota la palabra vacía y [^]generalmente se escribe .(cualquier carácter). Entonces, con un poco de reescritura ()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*.

Puede ver este ejemplo aquí y probar Vcsn en línea allí .

98%
fuente
66
Cierto, pero feo, y solo factible para conjuntos de caracteres pequeños. No desea hacer esto con cadenas Unicode :-)
reinierpost
Hay más herramientas que lo permiten, una de las más impresionantes es Ragel . Allí se escribiría como (any * - ('jeje' any *)) para la coincidencia alineada al inicio o (any * - ('jeje' any *)) para no alineados.
Peter K
1
@reinierpost: ¿por qué es feo y cuál es el problema con Unicode? No puedo estar de acuerdo en ambos. (No tengo experiencia con vcsn, pero sí con DFA).
Peter K
3
@PedroGimeno Cuando anclaste, ¿te aseguraste de poner esta expresión regular en parens primero? De lo contrario, las precencias entre las anclas y |no jugarán bien. '^(()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*)$'.
akim
1
Creo que vale la pena señalar que este método es para hacer coincidir líneas que no son la palabra 'hede', en lugar de líneas que no contienen la palabra 'hede', que es lo que solicitó el OP. Vea mi respuesta para el último.
Pedro Gimeno
51

Aquí hay una buena explicación de por qué no es fácil negar una expresión regular arbitraria. Sin embargo, tengo que estar de acuerdo con las otras respuestas: si esto no es una pregunta hipotética, entonces una expresión regular no es la opción correcta aquí.

Josh Lee
fuente
10
Algunas herramientas, y específicamente mysqldumpslow, solo ofrecen esta forma de filtrar datos, por lo que, en tal caso, encontrar una expresión regular para hacer esto es la mejor solución además de reescribir la herramienta (MySQL AB / Sun no ha incluido varios parches para esto). / Oracle.
MGF
1
Exactamente análogo a mi situación. El motor de plantillas de Velocity usa expresiones regulares para decidir cuándo aplicar una transformación (escape html) y quiero que siempre funcione EXCEPTO en una situación.
Henno Vermeulen
1
¿Qué alternativa hay? Nunca he encontrado nada que pueda hacer una coincidencia de cadena precisa además de expresiones regulares. Si OP está usando un lenguaje de programación, puede haber otras herramientas disponibles, pero si él / ella está usando no escribir código, probablemente no haya otra opción.
kingfrito_5005
2
Uno de los muchos escenarios no hipotéticos en los que una expresión regular es la mejor opción disponible: estoy en un IDE (Android Studio) que muestra la salida del registro, y las únicas herramientas de filtrado proporcionadas son: cadenas simples y expresiones regulares. Intentar hacer esto con cadenas simples sería un completo fracaso.
LarsH
48

Con anticipación negativa, la expresión regular puede coincidir con algo que no contiene un patrón específico. Esto es respondido y explicado por Bart Kiers. ¡Gran explicación!

Sin embargo, con la respuesta de Bart Kiers, la parte de anticipación pondrá a prueba de 1 a 4 caracteres por delante, mientras que coincide con cualquier carácter individual. Podemos evitar esto y dejar que la parte anticipada revise todo el texto, asegúrese de que no haya 'hede', y luego la parte normal (. *) Puede comer todo el texto al mismo tiempo.

Aquí está la expresión regular mejorada:

/^(?!.*?hede).*$/

Tenga en cuenta que el cuantificador diferido (*?) En la parte de búsqueda anticipada negativa es opcional, puede usar un cuantificador codicioso (*) en su lugar, dependiendo de sus datos: si 'hede' se presenta y en la mitad inicial del texto, el cuantificador diferido puede se más rápido; de lo contrario, el cuantificador codicioso será más rápido. Sin embargo, si 'hede' no se presenta, ambos serían igual de lentos.

Aquí está el código de demostración .

Para obtener más información sobre lookahead, consulte el excelente artículo: Dominar Lookahead y Lookbehind .

Además, consulte RegexGen.js , un generador de expresiones regulares de JavaScript que ayuda a construir expresiones regulares complejas. Con RegexGen.js, puede construir la expresión regular de una manera más legible:

var _ = regexGen;

var regex = _(
    _.startOfLine(),             
    _.anything().notContains(       // match anything that not contains:
        _.anything().lazy(), 'hede' //   zero or more chars that followed by 'hede',
                                    //   i.e., anything contains 'hede'
    ), 
    _.endOfLine()
);
amobiz
fuente
3
para verificar simplemente si la cadena dada no contiene str1 y str2:^(?!.*(str1|str2)).*$
S.Serpooshan
1
Sí, o puede usar el cuantificador diferido ^(?!.*?(?:str1|str2)).*$, dependiendo de sus datos. Se agregó el ?:ya que no necesitamos capturarlo.
amobiz
Esta es, con mucho, la mejor respuesta por un factor de 10xms. Si agregó su código jsfiddle y los resultados en la respuesta, las personas podrían notarlo. Me pregunto por qué la versión perezosa es más rápida que la codiciosa cuando no hay hede. ¿No deberían tomar la misma cantidad de tiempo?
user5389726598465
Sí, toman la misma cantidad de tiempo ya que ambos prueban todo el texto.
amobiz
41

Puntos de referencia

Decidí evaluar algunas de las opciones presentadas y comparar su rendimiento, así como utilizar algunas características nuevas. Evaluación comparativa en .NET Regex Engine: http://regexhero.net/tester/

Texto de referencia:

¡Las primeras 7 líneas no deberían coincidir, ya que contienen la Expresión buscada, mientras que las 7 líneas inferiores deberían coincidir!

Regex Hero is a real-time online Silverlight Regular Expression Tester.
XRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex HeroRegex HeroRegex HeroRegex HeroRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her Regex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.Regex Hero
egex Hero egex Hero egex Hero egex Hero egex Hero egex Hero Regex Hero is a real-time online Silverlight Regular Expression Tester.
RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRegex Hero is a real-time online Silverlight Regular Expression Tester.

Regex Her
egex Hero
egex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her is a real-time online Silverlight Regular Expression Tester.
Nobody is a real-time online Silverlight Regular Expression Tester.
Regex Her o egex Hero Regex  Hero Reg ex Hero is a real-time online Silverlight Regular Expression Tester.

Resultados:

Los resultados son iteraciones por segundo como la mediana de 3 carreras - Mayor número = mejor

01: ^((?!Regex Hero).)*$                    3.914   // Accepted Answer
02: ^(?:(?!Regex Hero).)*$                  5.034   // With Non-Capturing group
03: ^(?>[^R]+|R(?!egex Hero))*$             6.137   // Lookahead only on the right first letter
04: ^(?>(?:.*?Regex Hero)?)^.*$             7.426   // Match the word and check if you're still at linestart
05: ^(?(?=.*?Regex Hero)(?#fail)|.*)$       7.371   // Logic Branch: Find Regex Hero? match nothing, else anything

P1: ^(?(?=.*?Regex Hero)(*FAIL)|(*ACCEPT))  ?????   // Logic Branch in Perl - Quick FAIL
P2: .*?Regex Hero(*COMMIT)(*FAIL)|(*ACCEPT) ?????   // Direct COMMIT & FAIL in Perl

Como .NET no admite verbos de acción (* FAIL, etc.) no pude probar las soluciones P1 y P2.

Resumen:

Intenté probar la mayoría de las soluciones propuestas, algunas optimizaciones son posibles para ciertas palabras. Por ejemplo, si las primeras dos letras de la cadena de búsqueda no son las mismas, la respuesta 03 se puede ampliar para dar como ^(?>[^R]+|R+(?!egex Hero))*$resultado una pequeña ganancia de rendimiento.

Pero la solución general más rápida y legible en términos de rendimiento parece ser 05 usando una declaración condicional o 04 con el cuantificador posesivo. Creo que las soluciones Perl deberían ser aún más rápidas y más fáciles de leer.

Falco
fuente
55
También deberías ^(?!.*hede)tomar el tiempo . /// Además, probablemente sea mejor clasificar las expresiones para el corpus coincidente y el corpus no coincidente por separado porque generalmente es un caso en el que la mayoría de las líneas coinciden o la mayoría de las líneas no.
ikegami el
32

No regex, pero he encontrado lógico y útil usar greps en serie con tubería para eliminar el ruido.

p.ej. buscar un archivo de configuración de apache sin todos los comentarios

grep -v '\#' /opt/lampp/etc/httpd.conf      # this gives all the non-comment lines

y

grep -v '\#' /opt/lampp/etc/httpd.conf |  grep -i dir

La lógica de los greps en serie es (no es un comentario) y (coincide con dir)

kiwalk
fuente
2
Creo que está pidiendo la versión de la expresión regulargrep -v
Angel.King.47
99
Esto es peligroso. También pierde líneas comogood_stuff #comment_stuff
Xavi Montero
29

con esto, evitas probar con anticipación en cada posición:

/^(?:[^h]+|h++(?!ede))*+$/

equivalente a (para .net):

^(?>(?:[^h]+|h+(?!ede))*)$

Vieja respuesta:

/^(?>[^h]+|h+(?!ede))*$/
Casimir et Hippolyte
fuente
77
Buen punto; Me sorprende que nadie haya mencionado este enfoque antes. Sin embargo, esa expresión regular en particular es propensa a retroceder catastróficamente cuando se aplica a texto que no coincide. Así es como lo haría:/^[^h]*(?:h+(?!ede)[^h]*)*$/
Alan Moore
... o simplemente puedes hacer que todos los cuantificadores sean posesivos. ;)
Alan Moore
@ Alan Moore: yo también estoy sorprendido. Vi su comentario (y la mejor expresión regular en la pila) aquí solo después de publicar este mismo patrón en una respuesta a continuación.
ridgerunner
@ridgerunner, no tiene que ser el mejor. He visto puntos de referencia donde la respuesta superior funciona mejor. (Me sorprendió que aunque.)
Qtax
23

Lo mencionado anteriormente (?:(?!hede).)*es excelente porque puede anclarse.

^(?:(?!hede).)*$               # A line without hede

foo(?:(?!hede).)*bar           # foo followed by bar, without hede between them

Pero lo siguiente sería suficiente en este caso:

^(?!.*hede)                    # A line without hede

Esta simplificación está lista para agregar cláusulas "Y":

^(?!.*hede)(?=.*foo)(?=.*bar)   # A line with foo and bar, but without hede
^(?!.*hede)(?=.*foo).*bar       # Same
ikegami
fuente
20

Así es como lo haría:

^[^h]*(h(?!ede)[^h]*)*$

Preciso y más eficiente que las otras respuestas. Implementa la técnica de eficiencia de "desenrollar el ciclo" de Friedl y requiere mucho menos retroceso.

ridgerunner
fuente
17

Si desea hacer coincidir un carácter para negar una palabra similar a negar clase de caracteres:

Por ejemplo, una cadena:

<?
$str="aaa        bbb4      aaa     bbb7";
?>

No utilice:

<?
preg_match('/aaa[^bbb]+?bbb7/s', $str, $matches);
?>

Utilizar:

<?
preg_match('/aaa(?:(?!bbb).)+?bbb7/s', $str, $matches);
?>

El aviso "(?!bbb)."no es mirar hacia atrás ni mirar hacia adelante, es actual, por ejemplo:

"(?=abc)abcde", "(?!abc)abcde"
diyism
fuente
3
No hay "lookcurrent" en perl regexp's. Esto es realmente una anticipación negativa (prefijo (?!). El prefijo de búsqueda hacia adelante positivo sería (?=mientras que los prefijos de búsqueda hacia atrás correspondientes serían (?<!y (?<=respectivamente. Una búsqueda anticipada significa que lee los siguientes caracteres (por lo tanto, "adelante") sin consumirlos. Una mirada retrospectiva significa que verifica los caracteres que ya se han consumido.
Didier L
14

Una, en mi opinión, una variante más legible de la respuesta principal:

^(?!.*hede)

Básicamente, "coincide al comienzo de la línea si y solo si no tiene 'hede' en ella", por lo que el requisito se tradujo casi directamente en expresiones regulares.

Por supuesto, es posible tener múltiples requisitos de falla:

^(?!.*(hede|hodo|hada))

Detalles: el ancla ^ asegura que el motor de expresiones regulares no vuelva a intentar la coincidencia en cada ubicación de la cadena, lo que coincidiría con cada cadena.

El ancla ^ en el principio está destinada a representar el comienzo de la línea. La herramienta grep coincide con cada línea de una en una, en contextos en los que está trabajando con una cadena multilínea, puede usar el indicador "m":

/^(?!.*hede)/m # JavaScript syntax

o

(?m)^(?!.*hede) # Inline flag
Dannie P
fuente
Excelente ejemplo con negación múltiple.
Peter Parada
Una diferencia de la respuesta principal es que esto no coincide con nada, y que coincide con la línea completa si no tiene "hede"
Z. Khullah
13

El OP no especificó o Tagla publicación para indicar el contexto (lenguaje de programación, editor, herramienta) en el que se utilizará Regex.

Para mí, a veces necesito hacer esto mientras edito un archivo usando Textpad.

Textpad admite algunos Regex, pero no admite mirar hacia atrás o hacia atrás, por lo que se requieren algunos pasos.

Si estoy buscando retener todas las líneas que NO contienen la cadena hede, lo haría así:

1. Busque / reemplace todo el archivo para agregar una "Etiqueta" única al comienzo de cada línea que contenga cualquier texto.

    Search string:^(.)  
    Replace string:<@#-unique-#@>\1  
    Replace-all  

2. Elimine todas las líneas que contienen la cadena hede( la cadena de reemplazo está vacía):

    Search string:<@#-unique-#@>.*hede.*\n  
    Replace string:<nothing>  
    Replace-all  

3. En este punto, todas las líneas restantes NO contienen la cadena hede. Elimine la "Etiqueta" única de todas las líneas (la cadena de reemplazo está vacía):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  

Ahora tiene el texto original con todas las líneas que contienen la cadena hedeeliminada.


Si estoy buscando hacer algo más para solo líneas que NO contienen la cadena hede, lo haría así:

1. Busque / reemplace todo el archivo para agregar una "Etiqueta" única al comienzo de cada línea que contenga cualquier texto.

    Search string:^(.)  
    Replace string:<@#-unique-#@>\1  
    Replace-all  

2. Para todas las líneas que contienen la cadena hede, elimine la "Etiqueta" única:

    Search string:<@#-unique-#@>(.*hede)
    Replace string:\1  
    Replace-all  

3. En este punto, todas las líneas que comienzan con la "Etiqueta" única, NO contienen la cadena hede. Ahora puedo hacer mi Algo más solo con esas líneas.

4. Cuando termine, elimino la "Etiqueta" única de todas las líneas (la cadena de reemplazo está vacía):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  
Kevin Fegan
fuente
12

Como nadie más ha dado una respuesta directa a la pregunta que se hizo , lo haré.

La respuesta es que con POSIX grep, es imposible satisfacer literalmente esta solicitud:

grep "<Regex for 'doesn't contain hede'>" input

La razón es que POSIX grepsolo necesita trabajar con expresiones regulares básicas , que simplemente no son lo suficientemente potentes para realizar esa tarea (no son capaces de analizar lenguajes regulares, debido a la falta de alternancia y paréntesis).

Sin embargo, GNU grepimplementa extensiones que lo permiten. En particular, \|es el operador de alternancia en la implementación de BRE de GNU, \(y \)son los paréntesis. Si su motor de expresión regular admite alternancia, expresiones de paréntesis negativas, paréntesis y la estrella de Kleene, y puede anclarse al principio y al final de la cadena, eso es todo lo que necesita para este enfoque. Sin embargo [^ ... ], tenga en cuenta que los conjuntos negativos son muy convenientes además de esos, porque de lo contrario, debe reemplazarlos con una expresión de la forma (a|b|c| ... )que enumere todos los caracteres que no están en el conjunto, lo cual es extremadamente tedioso y demasiado largo, incluso más si todo el conjunto de caracteres es Unicode.

Con GNU grep, la respuesta sería algo como:

grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" input

(encontrado con Grail y algunas optimizaciones adicionales hechas a mano).

También puede usar una herramienta que implemente Expresiones regulares extendidas , como egrep, para eliminar las barras invertidas:

egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input

Aquí hay un script para probarlo (tenga en cuenta que genera un archivo testinput.txten el directorio actual):

#!/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)

En mi sistema imprime:

Files /dev/fd/63 and /dev/fd/62 are identical

como se esperaba.

Para aquellos interesados ​​en los detalles, la técnica empleada es convertir la expresión regular que coincide con la palabra en un autómata finito, luego invertir el autómata cambiando cada estado de aceptación a no aceptación y viceversa, y luego convirtiendo el FA resultante de nuevo a Una expresión regular.

Finalmente, como todos han notado, si su motor de expresión regular admite anticipación negativa, eso simplifica mucho la tarea. Por ejemplo, con GNU grep:

grep -P '^((?!hede).)*$' input

Actualización: Recientemente encontré la excelente biblioteca FormalTheory de Kendall Hopkins , escrita en PHP, que proporciona una funcionalidad similar a Grail. Utilizándolo, y un simplificador escrito por mí mismo, he podido escribir un generador en línea de expresiones regulares negativas con una frase de entrada (solo se admiten caracteres alfanuméricos y de espacio actualmente): http://www.formauri.es/personal/ pgimeno / misc / no-match-regex /

Para hedeello sale:

^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$

que es equivalente a lo anterior.

Pedro Gimeno
fuente
11

Desde la introducción de ruby-2.4.1, podemos usar el nuevo operador ausente en las expresiones regulares de Ruby

del documento oficial

(?~abc) matches: "", "ab", "aab", "cccc", etc.
It doesn't match: "abc", "aabc", "ccccabc", etc.

Por lo tanto, en su caso ^(?~hede)$hace el trabajo por usted

2.4.1 :016 > ["hoho", "hihi", "haha", "hede"].select{|s| /^(?~hede)$/.match(s)}
 => ["hoho", "hihi", "haha"]
aelor
fuente
9

A través del verbo PCRE (*SKIP)(*F)

^hede$(*SKIP)(*F)|^.*$

Esto omitiría por completo la línea que contiene la cadena exacta hedey coincide con todas las líneas restantes.

MANIFESTACIÓN

Ejecución de las partes:

Consideremos la expresión regular anterior dividiéndola en dos partes.

  1. Parte antes del |símbolo. La parte no debe coincidir .

    ^hede$(*SKIP)(*F)
  2. Parte después del |símbolo. Parte debe coincidir .

    ^.*$

PARTE 1

El motor Regex comenzará su ejecución desde la primera parte.

^hede$(*SKIP)(*F)

Explicación:

  • ^ Afirma que estamos al principio.
  • hede Coincide con la cadena hede
  • $ Afirma que estamos al final de la línea.

Entonces, la línea que contiene la cadena hedecoincidiría. Una vez que el motor regex ve el siguiente verbo (*SKIP)(*F)( Nota: podría escribir (*F)como(*FAIL) ), salta y hace que la coincidencia falle. |llamado alteración u operador lógico OR agregado al verbo PCRE que inturn coincide con todos los límites existentes entre todos y cada uno de los caracteres en todas las líneas, excepto que la línea contiene la cadena exacta hede. Vea la demostración aquí . Es decir, intenta hacer coincidir los caracteres de la cadena restante. Ahora se ejecutaría la expresión regular en la segunda parte.

PARTE 2

^.*$

Explicación:

  • ^ Afirma que estamos al principio. es decir, coincide con todos los inicios de línea excepto el de la hedelínea. Vea la demostración aquí .
  • .*En el modo Multilínea, .coincidiría con cualquier carácter, excepto los caracteres de nueva línea o retorno de carro. Y *repetiría el carácter anterior cero o más veces. Entonces .*coincidiría con toda la línea. Vea la demostración aquí .

    Hola, ¿por qué agregaste. * En lugar de. +?

    Porque .*coincidiría con una línea en blanco pero .+no coincidiría con un espacio en blanco. Queremos hacer coincidir todas las líneas hede, excepto que puede haber una posibilidad de líneas en blanco también en la entrada. así que debes usar en .*lugar de .+. .+repetiría el personaje anterior una o más veces. Ver .*coincide con una línea en blanco aquí .

  • $ El ancla de fin de línea no es necesaria aquí.

Avinash Raj
fuente
7

Puede ser más fácil mantener dos expresiones regulares en su código, una para hacer la primera coincidencia, y luego, si coincide, ejecute la segunda expresión regular para verificar casos atípicos que desee bloquear, por ejemplo, ^.*(hede).*entonces tenga la lógica apropiada en su código.

OK, admito que esto no es realmente una respuesta a la pregunta publicada y también puede usar un poco más de procesamiento que una sola expresión regular. Pero para los desarrolladores que vinieron aquí buscando una solución de emergencia rápida para un caso atípico, esta solución no debe pasarse por alto.

andrew pate
fuente
6

Otra opción es agregar un look-ahead positivo y verificar si heheestá en algún lugar de la línea de entrada, entonces lo negaríamos, con una expresión similar a:

^(?!(?=.*\bhede\b)).*$

con límites de palabras.


La expresión se explica en el panel superior derecho de regex101.com , si desea explorarla / simplificarla / modificarla, y en este enlace , puede ver cómo coincidiría con algunas entradas de muestra, si lo desea.


Circuito RegEx

jex.im visualiza expresiones regulares:

ingrese la descripción de la imagen aquí

Emma
fuente
5

El lenguaje TXR admite la negación regex.

$ txr -c '@(repeat)
@{nothede /~hede/}
@(do (put-line nothede))
@(end)'  Input

Un ejemplo más complicado: hacer coincidir todas las líneas que comienzan ay terminan con z, pero no contienen la subcadena hede:

$ txr -c '@(repeat)
@{nothede /a.*z&~.*hede.*/}
@(do (put-line nothede))
@(end)' -
az         <- echoed
az
abcz       <- echoed
abcz
abhederz   <- not echoed; contains hede
ahedez     <- not echoed; contains hede
ace        <- not echoed; does not end in z
ahedz      <- echoed
ahedz

La negación Regex no es particularmente útil por sí sola, pero cuando también tiene intersección, las cosas se ponen interesantes, ya que tiene un conjunto completo de operaciones de conjunto booleano: puede expresar "el conjunto que coincide con esto, excepto las cosas que coinciden con eso".

Kaz
fuente
Tenga en cuenta que también es la solución para expresiones regulares basadas en ElasticSearch Lucene.
Wiktor Stribiżew
4

La siguiente función lo ayudará a obtener el resultado deseado

<?PHP
      function removePrepositions($text){

            $propositions=array('/\bfor\b/i','/\bthe\b/i'); 

            if( count($propositions) > 0 ) {
                foreach($propositions as $exceptionPhrase) {
                    $text = preg_replace($exceptionPhrase, '', trim($text));

                }
            $retval = trim($text);

            }
        return $retval;
    }


?>
Daniel Nyamasyo
fuente
2

^ ((?! hede).) * $ es una solución elegante, excepto porque consume caracteres que no podrá combinar con otros criterios. Por ejemplo, supongamos que desea verificar la no presencia de "hede" y la presencia de "jaja". Esta solución funcionaría porque no consumirá caracteres:

^ (?!. \ bhede \ b) (? =. \ bhaha \ b)

cloudhopperpilot
fuente
1

Cómo usar los verbos de control de retroceso de PCRE para que coincidan con una línea que no contiene una palabra

Aquí hay un método que no he visto usado antes:

/.*hede(*COMMIT)^|/

Cómo funciona

Primero, trata de encontrar "hede" en algún lugar de la línea. Si tiene éxito, en este punto, (*COMMIT)le dice al motor que no solo retroceda en caso de falla, sino que no intente ninguna otra coincidencia en ese caso. Luego, intentamos hacer coincidir algo que no puede coincidir (en este caso ^).

Si una línea no contiene "hede", la segunda alternativa, un subpatrón vacío, coincide con la cadena de asunto.

Este método no es más eficiente que una anticipación negativa, pero pensé que lo lanzaría aquí en caso de que alguien lo encuentre ingenioso y lo use para otras aplicaciones más interesantes.

jaytea
fuente
0

¡Una solución más simple es usar el operador no !

Su declaración if deberá coincidir con "contiene" y no con "excluir".

var contains = /abc/;
var excludes =/hede/;

if(string.match(contains) && !(string.match(excludes))){  //proceed...

Creo que los diseñadores de RegEx anticiparon el uso de no operadores.

user1691651-John
fuente
0

Tal vez encuentre esto en Google mientras intenta escribir una expresión regular que pueda hacer coincidir segmentos de una línea (a diferencia de líneas completas) que no contienen una subcadena. Tócame un tiempo para averiguar, así que compartiré:

Dada una cadena: <span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>

Quiero hacer coincidir las <span>etiquetas que no contienen la subcadena "mala".

/<span(?:(?!bad).)*?>coincidirá <span class=\"good\">y <span class=\"ugly\">.

Observe que hay dos conjuntos (capas) de paréntesis:

  • El más interno es para la anticipación negativa (no es un grupo de captura)
  • Ruby interpretó lo más externo como un grupo de captura, pero no queremos que sea un grupo de captura, así que agregué?: Al principio y ya no se interpreta como un grupo de captura.

Demo en Ruby:

s = '<span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>'
s.scan(/<span(?:(?!bad).)*?>/)
# => ["<span class=\"good\">", "<span class=\"ugly\">"]
BrunoFacca
fuente
0

Con ConyEdit , puede usar la línea de comando cc.gl !/hede/para obtener líneas que no contienen la coincidencia de expresiones regulares, o usar la línea de comando cc.dl /hede/para eliminar las líneas que contienen la coincidencia de expresiones regulares. Tienen el mismo resultado.

Donald
fuente
0

Quería añadir otro ejemplo de si usted está tratando de igualar toda una línea que contiene la cadena X , pero ¿también no contiene cadena de Y .

Por ejemplo, supongamos que queremos verificar si nuestra URL / cadena contiene " golosinas sabrosas ", siempre que no contenga también " chocolate " en ninguna parte.

Este patrón de expresiones regulares funcionaría (también funciona en JavaScript)

^(?=.*?tasty-treats)((?!chocolate).)*$

(banderas globales de líneas múltiples, por ejemplo)

Ejemplo interactivo: https://regexr.com/53gv4

Partidos

(Estas URL contienen "golosinas sabrosas" y tampoco contienen "chocolate")

  • example.com/tasty-treats/strawberry-ice-cream
  • example.com/desserts/tasty-treats/banana-pudding
  • example.com/tasty-treats-overview

No coincide

(Estas URL contienen "chocolate" en alguna parte, por lo que no coincidirán aunque contengan "golosinas sabrosas")

  • example.com/tasty-treats/chocolate-cake
  • example.com/home-cooking/oven-roasted-chicken
  • example.com/tasty-treats/banana-chocolate-fudge
  • example.com/desserts/chocolate/tasty-treats
  • example.com/chocolate/tasty-treats/desserts
Matthew Rideout
fuente