- En
rede Python,$puede coincidir no solo con el fin de la cadena, sino también con la posición antes del último salto de línea al final, incluso si el modo multilínea está desactivado - Aunque
^parezca significar “inicio de la cadena”, no conviene asumir que$se comporta de forma completamente simétrica; su significado real varía según la implementación de expresiones regulares - Los resultados de
$,\zy\Zpara"cat\n"difieren entre PHP, ECMAScript, Python, Go, Java 8, .NET 7.0 y Rust, y\zde Python se agregó recién en Python 3.14 - Si se permite el salto de línea final,
$en modo multilínea coincide con"cat\n"en todas las plataformas de la tabla, pero si se quiere coincidir solo con el final excluyendo el salto de línea, la elección de la sintaxis cambia - Si no se debe coincidir con el último salto de línea, en la mayoría de las plataformas se usa
\z, y en versiones anteriores a Python 3.14 y en ECMAScript hay que considerar alternativas distintas
Dónde coincide $ en re de Python
- En el módulo de expresiones regulares
rede Python,$puede coincidir con el fin de la cadena o justo antes del último salto de línea al final de la cadena, incluso si el modo multilínea está desactivado cat$parece simple porque coincide con"lolcat"y no coincide con"internet cat video", pero si hay un salto de línea al final, como en"cat\n", puede comportarse distinto de lo esperado- Cuando se especifica
re.MULTILINE,$coincide con el fin de la cadena y con el final de cada línea, es decir, justo antes de cada salto de línea - Incluso con el valor predeterminado,
$coincide con el fin de la cadena y, si hay un salto de línea al final de la cadena, también coincide justo antes de ese salto de línea
Coincidir excluyendo el último salto de línea
- Para coincidir estrictamente solo con el fin de la cadena, puede que
$no sea suficiente, y\zy\Zpasan a ser candidatos como anclas de fin - Según la documentación de expresiones regulares de Python y otras explicaciones de sintaxis de expresiones regulares, el soporte y el significado de
\zy\Zvarían según la implementación - Las diferencias para
"cat\n"son las siguientes- PHP:
"cat$"coincide independientemente de si está en modo multilínea;"cat\z"no coincide, y"cat\Z"sí coincide - ECMAScript:
"cat$"en modo multilínea coincide;"cat$"sin modo multilínea no coincide, y\zy\Zno son compatibles - Python:
"cat$"coincide independientemente de si está en modo multilínea, y"cat\z"y"cat\Z"no coinciden con"cat\n" - Go y Rust:
"cat$"en modo multilínea coincide;"cat$"sin modo multilínea y"cat\z"no coinciden, y\Zno es compatible - Java 8 y .NET 7.0:
"cat$"coincide independientemente de si está en modo multilínea;"cat\z"no coincide, y"cat\Z"sí coincide
- PHP:
\zde Python se agregó recién en Python 3.14, y no era compatible en versiones anteriores- Si se permite el salto de línea final,
$en modo multilínea coincide de forma consistente con"cat\n"en todas las plataformas de la tabla - Para no coincidir con el salto de línea final, en la mayoría de las plataformas se usa
\z; en versiones anteriores a Python 3.14 se debe usar\Z, y en ECMAScript se debe usar$sin modo multilínea - Los datos de la tabla se recopilaron en regex101.com y no se probaron en runtimes reales
1 comentarios
Comentarios de Hacker News
Desde hace mucho he pensado en
^como el "inicio de línea" y en$como el "final de línea"Al trabajar con expresiones regulares, muchas veces se procesa el texto línea por línea, así que a menudo el resultado termina siendo el mismo, pero la forma en que sigo pensando en esos operadores está más cerca de una "línea" que de una "cadena"
Supongo que mucho de eso viene de haber conocido las regex con
grep, así que me quedó la costumbre de ver la entrada no como una cadena sino como líneasLlevo casi 20 años usando regex, pero creo que es la primera vez que escucho decir que
$es el final de la cadena; siempre lo he considerado el final de línea^como el "inicio de la cadena"En la práctica, así como
$es el "final de línea",^también es el "inicio de línea", y el inicio de la cadena se parece más a\A, mientras que el final de la cadena se parece más a\Z$por defecto se comporta como una afirmación de anticipación positiva sobre el final de la cadenaNo hace match ni consume el carácter de salto de línea
Solo en modo multilínea hace match en la posición del salto de línea, pero aun así parece que no lo consume
De hecho, usando
$no pude construir una regex que capturara el último carácter de una línea, consumiera el salto de línea y luego capturara el primer carácter de la siguiente; el grupo de captura simplemente termina en$grep, fue Vim lo que me metió esa idea en la cabezaLas regex POSIX y las regex de Python son distintas
En general, la sintaxis de las regex no es universal, así que hay que revisar la documentación de la implementación que estés usando
Según el capítulo 9 de POSIX, las regex operan sobre cadenas, pero algunas utilidades limitan el procesamiento a una línea por vez
Además,
$se define como un ancla fijada al final de la cadena objetivo, así que al final si$significa fin de cadena o fin de línea depende de la utilidad o del modoHerramientas comunes como
grep,sed,awky Python trabajan por defecto línea por línea, así que normalmente se trata como fin de líneaNo existe una única sintaxis de regex universal
Si no sabes qué lenguaje y qué opciones se están usando, no puedes leer ni escribir regex de forma confiable
https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1...
Este tema es perfecto para presentar a Robert Elder a quienes no lo conocen
Hace muy buen contenido en YouTube y en su blog, y en su serie sobre regex profundiza bastante en las diferencias de comportamiento entre las implementaciones de varios herramientas
Su video reciente también está bueno: https://www.youtube.com/watch?v=ys7yUyyQA-Y
Tiene mucho contenido que probablemente interese a lectores de HN, y también toca temas como la realidad y las dificultades de la consultoría
https://www.youtube.com/@RobertElderSoftware
https://blog.robertelder.org/
https://blog.robertelder.org/regular-expressions/
https://www.youtube.com/watch?v=cK87ktENPrI
Cuando aprendí Perl, las regex fueron de las primeras cosas que realmente interioricé, y hasta hoy Perl sigue ocupando un lugar cómodo en mi cabeza gracias al libro "Camel"
Lo más importante que sé ahora es que depende de la implementación, así que ya tengo el hábito de sacar la hoja de referencia cada vez que voy a trabajar con algo
Por ejemplo, me molesta que las regex de Emacs no soporten caracteres de palabra del tipo
\wy que haya que usar clases de caracteres como\s_-, pero aun así creo que Emacs es de lo mejor en documentación y facilidad de descubrimientoAlgunas utilidades requieren escapar los paréntesis y otras no, y en algunos casos ese comportamiento se puede configurar y en otros no
Ya pasé por las etapas de confusión, frustración y negación, y ahora simplemente lo acepto
El concepto es el mismo en todos lados, pero cambia el dialecto
Sobre todo en el shell, antes que ponerme a recordar si
sed/grep/awkson GNU o BSD, muchas veces prefiero meterperlen el pipelinePerl parece como si un gato hubiera caminado sobre el teclado
Casi puedo oír a un montón de malos reclutadores agregando "¿cómo se hace match con el final de una cadena en regex?" a su lista de preguntas trampa
Se siente raro dejar fuera a Perl en una lista sobre regex
En la documentación de
perlre,$se explica así: hace match con el final de la cadena, o con la posición anterior al salto de línea al final de la cadena, o, si usas/m, con la posición anterior a cualquier salto de líneaTambién da la impresión de que eso muestra hasta qué punto Perl ha quedado fuera del foco hoy en día
Raku, antes Perl 6, definió
^y$como inicio/fin de cadena, e introdujo^^y$$para inicio/fin de líneaNo tiene modo multilínea y tampoco lo necesita
También tiene
\hpara espacio horizontal y\vpara espacio verticalComo se replanteó y reescribió por completo, tuvo la ventaja de poder aprender del hecho de que el comportamiento anterior sorprendía a la gente
Se siente como si hubieran mezclado al azar una sintaxis tipo line noise que uno aprendió durante décadas
Parecería más claro si el valor predeterminado hubiera sido el contrario
Habría sido más natural usar
^y$para líneas, y^^y$$para cadenasPorque se ve como
^^line1$\n^line2$\n^line3$\n$Además, Perl 6 no está en todos lados, pero Perl 5 sí
^^se ve más “como inicio” que^Normalmente proceso líneas metiéndolas en la regex, así que usar
^y$simples para la cadena completa conserva cierta compatibilidad hacia atrásNo estoy seguro de que alguien considere que las regex estén estandarizadas
Cada vez que cambiaba de entorno, siempre tenía que volver a aprenderlas
Seguro hay más dialectos de regex, pero no me los he topado y con los que conozco resuelvo casi todo
Es parecido a manejar un auto rentado
Funciona un poco distinto a mi coche, le faltan algunas funciones y tiene otras extra, pero en general casi todos se parecen bastante
Así que al menos yo sí diría que las regex están estandarizadas mediante varios estándares oficiales públicos
https://open-std.org/jtc1/sc22/…
https://pubs.opengroup.org/onlinepubs/9699919799/…
https://262.ecma-international.org/14.0/…
Muchos sistemas, incluido JavaScript, implementaron PCRE, porque Perl añadió muchas extensiones útiles al esquema POSIX
Según recuerdo, RE2 busca contener los problemas de rendimiento y los comportamientos extraños de los sistemas anteriores, y yo creía que estaba implementado entero en Go
Después supe que RE2 apareció antes que Go
Aun así, el significado de
$y la forma de cambiar al modo multilínea suelen ser bastante consistentesLa gente está confundiendo cadenas y líneas
Una cadena es una secuencia de caracteres, y una línea puede verse de dos maneras
Si el salto de línea se considera terminador de línea, una línea son cero o más caracteres que no son salto de línea seguidos de un salto de línea, y si al final no hay salto de línea no es una línea completa
POSIX usa esta perspectiva
Si el salto de línea se considera separador de líneas, una línea es una secuencia de cero o más caracteres que no son salto de línea
En cualquiera de las dos interpretaciones, el contenido de la línea termina antes del salto de línea
La semántica de
^y$está basada en líneas, tanto en modo de una sola línea como en modo multilíneaPara semántica basada en cadenas, o en archivos completos si estás tratando archivos, hay que usar
\Ay\Zo su equivalenteAmbas interpretaciones tienen ventajas
Al transmitir texto por conexión serial, tratar el salto de línea como terminador ayuda a saber si ya recibiste una línea completa
En archivos de texto, ver el salto de línea como separador puede ser más cómodo porque la última línea no queda en un estado inválido, pero tener un terminador de línea permite detectar líneas escritas de forma incompleta
Por esto ha habido varios bugs graves en apps basadas en Ruby
Siempre hay que usar
\A\zhttps://homakov.blogspot.com/2012/05/saferweb-injects-in-var...
https://sakurity.com/blog/2015/02/28/openuri.html
https://sakurity.com/blog/2015/06/04/mongo_ruby_regexp.html