Capítulo 5. Expresiones regulares

Realicemos un programa mucho más interesante. Es el momento de comprobar si una cadena satisface una descripción, que llamaremos patrón.

En estos patrones existen algunos caracteres y combinaciones de caracteres que tienen un significado especial, y son:

Tabla 5-1. Caracteres especiales en expresiones regulares

SímboloDescripción>
[]Especificación de rango. (p.e. [a-z] representa una letra en el rango de la a a la z
\wLetra o dígito; es lo mismo que [0-9A-Za-z]
\WNi letra, ni dígito
\sEspacio, es lo mismo que [ \t\n\r\f]
\SNo espacio
\dDígito; es lo mismo que [0-9]
\DNo dígito
\bBackspace (0x08) (sólo si aparece en una especificación de rango)
\bLímite de palabra (sólo si no aparece en una especificación de rango)
\BNo límite de palabra
*Cero o más repeticiones de lo que precede
+Una o más repeticiones de lo que precede
[m,n]Al menos m y como máximo n de lo que precede
?Al menos una repetición de lo que precede; es lo mismo que [0,1]
|Puede coincidir con lo que precede o con lo que sigue
()Agrupamiento

El término común para esto patrones que utilizan este extraño vocabulario es expresión regular. En Ruby, como en Perl, normalmente están rodeadas por barras inclinadas en vez de por comillas dobles. Si nunca antes se ha trabajado con expresiones regulares, es probable que parezcan cualquier cosa excepto regulares, pero sería inteligente dedicar algún tiempo a familiarizarse con ellas. Tienen un poder expresivo en su concisión que puede evitar muchos dolores de cabeza (y muchas líneas de código) si se necesita realizar coincidencia de patrones o cualquier otro tipo de manipulación con cadenas de texto.

Por ejemplo, supongamos que queremos comprobar si una cadena se ajusta a esta descripción: "Comienza con una f minúscula, a la que sigue exactamente una letra mayúscula y opcionalmente cualquier cosa detrás de ésta, siempre y cuando no haya más letras minúsculas." Si se es un experimentado programador en C probablemente se haya escrito este tipo de código docenas de veces, ¿verdad? Admitádmoslo, es difícil mejorarlo. Pero en Ruby sólamente es necesario solicitar que se verifique la cadena contra la siguiente expresión regular /^f[A-Z][^a-z]*$/.

Y que decir de "¿Contiene la cadena un número hexadecimal entre ángulos?" No hay problema.


ruby> def chab(s) # contiene la cadena un hexadecinal entre ángulos
ruby|   (s =~ /<0[Xx][\dA-Fa-f]+>/) != nil 
ruby| end 
nil 
ruby> chab "Este no es" 
false 
ruby> chab "¿Puede ser esta? (0x35)" # entre paréntesis, no ángulos 
false 
ruby> chab "¿O esta? <0x38z7e>"  # letra errónea 
false 
ruby> chab "OK esta si; <0xfc0004>" 
true 

Aunque inicialmente las expresiones regulares pueden parecer enigmáticas, se gana rápidamente satisfacción al ser capaz de expresarse con tanta economía.

A continuación se presenta un pequeño programa que nos permitirá experimentar con las expresiones regulares. Almacenémoslo como regx.rb, se ejecuta introduciendo en la línea de comandos ruby regx.rb


# necesita un terminal ANSI!!!
 
st = "\033[7m"
en = "\033[m"
 
while TRUE
  print "str> "
  STDOUT.flush
  str = gets
  break if not str
  str.chop!
  print "pat> "
  STDOUT.flush
  re = gets
  break if not re
  re.chop!
  str.gsub! re, "#{st}\\&#{en}"
  print str, "\n"
end
print "\n"

El programa necesita dos entradas, una con la cadena y otra con la expresión regular. La cadena se comprueba contra la expresión regular, a continuación muestra todas las partes de la cadena que coindicen con el patrón en vídeo inverso. No nos preocupemos de los detalles; analizaremos el código posteriormente.


str> foobar
pat> ^fo+
foobar
~~~

Lo resaltado es lo que aparecerá en vídeo inverso como resultado de la ejecución del programa. La cadena '~~~' es en beneficio de aquellos que usen visualizadores en modo texto.

Probemos algunos ejemplos más.


str> abc012dbcd555
pat> \d
abc012dbcd555
   ~~~    ~~~

Sorprendentemente y como indica la tabla al principio de este capítulo: \d no tiene nada que ver el carácter d, sino que realiza la coincidencia con un dígito.

¿Qué pasa si hay más de una forma de realizar la coincidencia con el patrón?.


str> foozboozer
pat> f.*z
foozboozer
~~~~~~~~

se obtiene foozbooz en vez de fooz porque las expresiones regulares tratan de obtener la coincidencia más larga posible.

A continuación se muestra un patrón para aislar la hora de un campo limitada por dos puntos.


str> WedFeb  7 08:58:04 JST 2001
pat> [0-9]+:[0-9]+(:[0-9]+)?
WedFeb  7 08:58:04 JST 2001
          ~~~~~~~~ 

=~ es el operador de coincidencia con expresiones regulares; devuelve la posición en la cadena donde se ha producido una coincidencia o nil si no la hay.


ruby> "abcdef" =~ /d/
3
ruby> "aaaaaa" =~ /d/
nil