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 grep
solo 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 grep
implementa 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.txt
en 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 hede
ello sale:
^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$
que es equivalente a lo anterior.
([^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