Perl: Expresiones Regulares



1.- Expresiones Regulares

Una expresión regular es una forma de expresar gramaticalmente la estructura de cualquier cadena alfanumérica.

La forma en que se usan en Perl es muy similar a la que utiliza el comando grep de Unix. Dicho comando se suele utilizar para buscar en los ficheros de entrada (o bien en la entrada estándar) las líneas que contienen un patrón especificado.

Comando grep del Unix

La forma de utilizar este comando es bastante sencilla... lo complicado es especificar la expresión regular que defina los patrones que queremos buscar.

Desde la línea de órdenes llamamos al comando grep con la opción -e para indicarle la expresión regular que queremos utilizar, y justo después le indicamos los ficheros donde queremos buscar esa expresión regular. Por ejemplo:

	$ grep -e "grep\|Unix" *.txt
	
nos buscará en los ficheros con extensión .txt del directorio donde estamos en ese momento cualquier línea que contenga las cadenas grep o Unix.

Como fichero de ejemplo, todos utilizaremos el mismo, cuyo contenido es el que aparece a continuación:

	Esto es simplemente un fichero
	de texto normalito, para hacer pruebas
	con el comando grep
	del Unix.
	
	Para hacer recuentos de cadenas y todo eso
	teniendo en cuenta las mayusculas y minusculas (Unix
	UnIx
	uNiX
	gReP
	
	y todo eso...
	

Entre las muchas opciones del comando, podemos especificarle que no nos imprima las líneas donde ha encontrado el patrón, sino que nos imprima el número de veces que se cumple dicho patrón. Así, el siguiente ejemplo

	$ grep -c -e "grep\|Unix" *.txt
	
cuenta el número de veces que cada fichero cumple ese patrón. Un ejemplo de salida obtenida sería:
	3
	

Otra opción interesante es la posibilidad de indicarle que no haga distinciones entre mayúsculas y minúsculas, usando la opción -i. Así, no hará distinción entre Unix o uNiX, por ejemplo. Veamoslo con un ejemplo:

	$ grep -c -e "grep\|Unix" *.txt

	6
	

Otra opción interesante es que nos indique la línea en la que se produjo la coincidencia entre el patrón y la línea del fichero. Esto lo podemos conseguir con la opción -n.

	$ grep -n -e "grep\|Unix" *.txt
	

Por último, a veces nos interesa especificar una expresión regular que no queremos que se cumpla. Eso lo podemos hacer con la opción -v.

	$ grep -v -c -e "grep\|Unix" *.txt
	9
	

Además de las alternativas en la expresión regular, especificada con el caracter |, podemos especificar rangos de caracteres, utilizando los corchetes ([ ]); un caracter cualquiera utilizando el punto (.); que cierto sub-patrón aparezca como mucho una vez (?) o que aparezca cero o mas veces (*) o que aparezca una o mas veces (+); etc, etc.

Formato de las expresiones regulares en Perl.

Las expresiones regurales en Perl tienen la misma sintaxis que la utilizada por el comando grep de Unix.

Para utilizarlas, las debemos encerrar entre // (mientras que en grep se colocaban indicadas con -e y entre comillas). Normalmente devolverán un valor lógico indicando si la variable sobre la que queremos aplicarla contiene o no el patrón. Si no se indica otra cosa, por defecto comparará la expresión con la variable "por defecto" $_. Por ejemplo:

	/pepe/ 
devolverá el valor cierto si $_ contiene la cadena pepe.

El programa:

	$_ = "hola pepe";
	print "si\n"  if (/pepe/);
	
imprimirá si, ya que la expresión regular devulve el valor lógico de verdadero.

Si queremos utilizar otra variable diferente a la $_ debemos utilizar el operador =~ , por ejemplo:

	$cad =~ /pepe/ 
es cierto si la cadena pepe esta incluida en $cad

Las expresiones regulares son sensitivas a mayúsculas, así, la expresión:

	$cad = "Pepe come queso";
	$cad =~ /pepe/;
	
es falsa, ya que $cad no contiene la subcadena pepe, aunque contenga la subcadena Pepe.

El operador que hace lo contrario que el =~ es el !~ , así, la expresión:

	$cad = "Pepe come queso";
	$cad !~ /pepe/;
	
es cierta.

Construyendo expresiones regulares complejas

La forma en que podemos construir expresiones regulares más complejas que las vistas hasta ahora (especificadas por una cadena de caracteres simple) es la dada en las siguientes reglas:

A modo de resumen de todo lo dicho, véase la tabla siguiente:

. cualquier caracter salvo el de retorno de carro
^ indica que coincida al principio de la línea
$ indica que coincida al final de la línea
* aparezca 0 o más veces el caracter que lo precede
+ aparezca 1 o más veces el caracter que lo precede
? aparezca 0 o 1 veces el caracter que lo precede
[] indica un conjunto de caracteres que pueden aparecer
[^] indica un conjunto de caracteres que no pueden aparecer
| indica una disyunción, aparezca una de las opciones
() agrupa una serie de patrones en un simple elemento
{n} que coincida exactamente n veces
{n,} que coincida al menos n veces
{n,m} que coincida al menos n veces y no mas de m
\n un retorno de línea
\t un tabulador
\w un caracter alfanumérico (equivale a [a-zA-Z0-9_])
\W un caracter no alfanumérico (equivale a [^a-zA-Z0-9_])
\d un caracter numérico (equivale a [0-9])
\D un caracter no numérico (equivale a [^0-9])
\s un caracter de espaciado (espacio, tabulador, nueva línea, etc)
\S un caracter NO de espaciado
\b coincida con los límites de una palabra
\B coincida con el interior de una palabra
\033 un número octal (el 033)
\x1b un número hexadecimal (el 1B)
\| \[ \] \( \) \* \^ \/ \\ etc representan | [ ] ( ) * / \ (hay que escapar estos caracteres especiales)


Veamos algunos ejemplos ilustrativos:

d.luna "d" seguida de un caracter cualquiera y una "l" (del, dal, dzl, d5l, etc)
^funa "f" al principio de la cadena (fofo, farfolla, f35, etc)
^hol"hola" al principio de la cadena (hola, holita, etc)
e$una "e" al final de la cadena (este, ese, etc)
te$"te" al final de la cadena (este, paquete, etc)
ind*"in" seguido de cero o más caracteres "d" (in, ind, indd, etc)
.*cualquier cadena, sin retorno de carro
^$una cadena vacia
[qjk]una "q", o una "j" o una "k"
[^qjk]no sea "q", o una "j" o una "k"
[a-z]cualquier letra entre la "a" y la "z"
[^a-z]no sean letras minúsculas
[a-zA-Z]una letra minúscula o mayúscula
[a-z]+una secuencia no vacia de letras minúsculas
f.*cacoincide con p.e. "fca", "foca", "flaca", "flor vaca", etc
f.+cacoincide con los anteriores salvo con "fca"
fe?acoincide con "fa" y "fea"
^[ \t]*$una linea en blanco, o combinaciones de espacios y tabuladores
[-+]?\d*\.?\d*lo mismo que [-+]?[0-9]*\.?[0-9]* (números decimales)
( (\d{1,2})\/(\d{1,2})\/(\d{1,4}) )una fecha en el siguiente formato DD/MM/AAAA (13/02/1674 ó 8/2/23 pero también 99/99/9999)
pepe|juano "pepe" o "juan"
(pe|hue)coso "pecos" o "huecos"
(da)+o da o dada o dadada ...
[01]un "0" o un "1"
\/0una división por 0
\/ 0una división por 0 con un espacio
\/\s0una división por 0 con un caracter de espacio (espacio, tabulador, retorno de carro)
\/ *0una división por 0 con varios espacios
\/\s*0una división por 0 con posibles caracteres de espaciado
\/\s*0\.0*una división por 0 con posibles caracteres espaciado y acepta "0." "0.0" "0.00" etc
fia|fea|fuacoincida con "fia", "fea" o "fua"
f(i|e|u)acoincida con "fia", "fea" o "fua"
(fia|fea|fua)equivale a los dos anteriores
[fia|fea|fua]equivale a [fiaeu]

Los dos últimos ejemplos nos muestran que debemos ir con cuidado a la hora de colocar las alternativas en las expresiones regulares. Se suelen usar los paréntesis para encerrar las alternativas en las expresiones regulares y para agrupar patrones a recordar para después, mientras que se usan los corchetes para especificar clases.

A veces nos convendrá utilizar el contenido de una variable dentro de la expresión regular. Así, dependiendo del valor de esa variable podemos buscar diversos patrones (por ejemplo, buscar el contenido de la variable $a dentro de la variable $b).

Pues bien, podemos hacer uso de las variables dentro de las expresiones regulares directamente, como se ve en el siguiente ejemplo:

	#!/usr/bin/perl
	
	$a = <>;
	$b = <>;
	
	chop($a);
	chop($b);
	
	if( $b =~ /$a/ ) {
		print "$a esta incluida en $b \n";
	}
	
este programa lee de entrada estándar dos cadenas y comprueba si la primera está incluida en la segunda.

Pero se plantea un problema... ¿y si la expresión regular es algo más compleja? Supongamos que queremos comprobar si $a seguida de una "P" se encuentra en $b. En este caso no podemos poner if( $b =~ /$aP/ ) { porque Perl intentaría buscar en $b el contenido de la variable $aP, y no el de la variable $a seguida de una "P". En este caso hay que hacer algo como if( $b =~ /${a}P/ ) { (nótese el uso de las llaves rodeando el nombre de la variable).


2.- Sustitución

Además de identificar expresiones regulares, Perl puede hacer sustituciones basadas en los "emparejamientos" identificados en las expresiones. La forma de hacer eso es con la función s///.

Para hacer una sustitución de loco por Loco en la cadena $cad, usamos:

	$cad =~ s/loco/Loco/;
	
y para hacerlo en la cadena $_:
	s/loco/Loco/;
	

Opciones

Si en la cadena aparece más de una vez la cadena a reemplazar, si no indicamos lo contrario, sólo reemplaza la primera ocurrencia. Para hacer una sustitución global, tras el último / debemos colocar una g:
	$cad =~ s/loco/Loco/g;
	
La expresion retorna el número de sustituciones hechas, 0 (falso) o mayor que 0 (verdadero).

Si queremos replazar ocurrencias como lOco, lOcO, LoCo, etc por Loco, podemos hacer:

	$cad =~ s/[Ll][Oo][Cc][Oo]/Loco/g;
	
o más fácil, utilizamos la opcion i (de ignorar mayúsculas):
	$cad =~ s/loco/Loco/gi;
	
y hace una sustitución global e ignorando las mayúsculas.

Si lo que queremos es evaluar algo, en la segunda parte de la expresión regular, debemos utilizar la opción e.

En el siguiente ejemplo, se lee de entrada estándar (sobre $_) y para cada línea (se supone que tiene un número real, ¡con su punto y todo!) se realiza una operación matemática, quedando el resultado almacenado en $_. En este ejemplo se hace uso de una variable especial $& que "recuerda" cierta parte del patrón correspondiente a una parte de la expresión regular que hayamos encerrado entre paréntesis.

	while(<>) {
		s/(\d+\.\d+)/$&*100/e;
		print;
	}
	


3.- Translación

Para hacer una translación utilizamos la función tr///. Así, la siguiente expresión reemplaza cada "a" por "e", cada "b" por "d" y cada "c" por "f" en la cadena $cad:

	$cad =~ tr/abc/edf/;
	
la expresión retorna el número de sustituciones hechas.

El siguiente ejemplo cuenta el número de asteriscos en la cadena $cad:

	$contador = ( $cad =~ tr/*/*/ );
	

El siguiente ejemplo transforma a mayúsculas la cadena $_:

	tr/a-z/A-Z/;
	


4.- Recordando patrones

A veces, en una expresión regular conviene recordar los patrones encontrados para ser usados posteriormente (en la expresión o fuera de ella). Esto se consigue, encerrando los patrones a recordar entre paréntesis y referenciándolos mediante las variables $1, $2, ... $9 (de sólo lectura) fuera de la expresión, o con las variables \1, \2, ... \9 dentro de la expresión.

Por ejemplo, el siguiente programa, sustituye las letras mayúsculas por dichas letras pero entre asteriscos:

	$_ = "Hola Don Pepe";
	s/([A-Z])/\*\1\*/g;
	print "$_";
	
		Resultado:
		*H*ola *D*on *P*epe
	

En el siguiente ejemplo utilizamos los patrones encontrados fuera de la expresión regular, mediante la variable $1 para encontrar las palabras repetidas:

	if( /(\b.+\b) \1/ ) {
	   print "encontrada la palabra $1 \n";
	}
	

El siguiente ejemplo intercambia el primer y último caracteres de la cadena $_:

	s/^(.)(.*)(.)$/\3\2\1/;
	

El siguiente ejemplo intercambia las dos primeras palabras de la cadena $_:

	$_ = "Hola Don Pepe";
	s/^([^ ]*) *([^ ]*)/\2 \1/;
	print "$_";
	
		Resultado:
		Don Hola Pepe
	

Tras el uso de una expresión regular, podemos utilizar las variables especiales $`, $&, $', (las tildes esas son las que están junto con la [ y con la {) que guardan lo que hay antes, lo encontrado y lo que hay después en la cadena:

	$_ = "Hola Don Pepe";
	/on/;
	print "\n $` \n $& \n $' \n";
	   
		Resultado:
	   	Hola D
	   	on
	   	 Pepe
	


Pedro Angel Castillo Valdivieso: pedro@geneura.ugr.es
Equipo GeNeura
Departamento de Arquitectura y Tecnología de los Computadores
Universidad de Granada
tlf: +34-58-243163
fax: +34-58-248993