Introducción a las expresiones regulares

Una expresión regular es una plantilla formada por unos metacaracteres con significado propio que se utiliza para especificar patrones regulares sobre cadenas de texto. Como esta definición no aclara mucho los conceptos vamos a intentar aclarar más las cosas con algunos ejemplos prácticos. Un ejemplo de algo parecido a un expresión regular serían las plantillas para nombres de ficheros usando * y ?; en este caso, *.txt expresaría todos los ficheros cuyo nombre termina en .txt, pero una expresión regular va mucho más alla. Una expresión regular se puede construir, para concordar con, por ejemplo, una dirección de correo electrónico, un número, una fecha, una url, una etiqueta HTML, etc., es decir, cualquier cadena que cumpla unas determinadas características de regularidad.

PHP y expresiones regulares

PHP dispone de las siguientes funciones que permiten trabajar con expresiones regulares:

preg_match -- Verifica una expresión con una cadena

preg_match_all -- Verifica una expresión con una cadena

preg_replace -- Realiza una búsqueda de una expresión y una sustitución

preg_split -- Divide una cadena según una expresión

preg_quote -- Prepara los caracteres de expresiones

preg_grep -- Devuelve un array con los elementos que casen con la expresión

ereg -- Verifica una expresión con una cadena

ereg_replace -- Realiza una búsqueda de una expresión y una sustitución

eregi -- Realiza una búsqueda de una expresión sin diferenciar mayúsculas y minúsculas

eregi_replace -- Realiza una búsqueda de una expresión y una sustitución sin diferenciar mayúsculas y minúsculas

split -- divide la cadena en elementos de un array según una expresión regular

sql_regcase -- construye una expresión regular para buscar coincidencias sin diferenciar mayúsculas y minúsculas


Comprobar si un número es entero

function es_int($numero)

{

if (preg_match("/^(+|-)?[0-9]+$/",$numero))

return true;

else

return false;

}


Comprobar si un número es flotante

function es_float($numero)

{

if (preg_match("/^(+|-)?[0-9]+\.[0-9]*$/",$numero))

return true;

else

return false;

}


Comprobar si una variable es un número

function es_num($numero)

{

if (preg_match("/^(+|-)?[0-9]+\.?[0-9]*$/",$numero))

return true;

else

return false;

}


Comprobar si una cadena es una dirección de correo

function es_email$email)

{

if (preg_match("/^([0-9a-zA-Z]+)([._]([0-9a-zA-Z]+))*@([0-9a-zA-Z]+)([._-]([0-9a-zA-Z]+))*[.]([0-9a-zA-Z]){2}([0-9a-zA-Z])?$/",

$email)

return true;

else

return false;

}


Sustituir toda secuencia de más de un <br> por un <p>

function quita_br($texto)

{

$texto= (preg_replace("/<br>(<br>)+/","<p>",$texto));

return $texto;

}

Editar ficheros con expresiones regulares

Un de las principales ventajas de las expresiones regulares es poder editar ficheros, sustituir textos o generar nuevos ficheros manipulando el contenido de otros.


sed

Con sed podemos ejecutar:

       sed "s/expresión_regular/sustitución/gi" fichero

/g indica edición global y afecta a todas las apariciones del texto correspondiente a la expresión regular en el fichero. Si omitimos /g la sustitución sólo afecta a la primera aparición de cada línea. La opción /i evita la distinción entre mayúsculas y minúsculas.

La sinaxis de sed es limitada.


$ echo "aabbccdd"|sed "s/a/x/"
xabbccdd
$ echo "aabbccdd"|sed "s/a/x/g"
xxbbccdd
$ echo "aabbccdd"|sed "s/a\+/x/"
xbbccdd
$ echo "aabbccdd"|sed "s/a*/x/"
xbbccdd

En el primer ejemplo observamos como sólo sustituye la primera "a" por la "x". En el segundo sí ha sustituido todas las apariciones de "a" al haber añadido la opción /g.

En el tercer ejemplo se sustituye una o más apariciones de "a" por "x".

En el último obsevamos el resultado esperado, sustituir una secuencia de "a" por una x.


Palabras

echo "esto si es asi"|sed "s/\<si\>/no/g"
esto no es asi

En el anterior ejemplo vemos como sustituir una palabra por otra en un texto sin que afecte a las demás. Hemos sustituido la palabra "si" por "no" sin que afecte a la palabra "asi".


Mayúsculas

echo "esto si es asi"|sed "s/\<Si\>/no/gi"
esto no es asi
$ echo "esto si es asi"|sed "s/\<Si\>/no/g"
esto si es asi

Vemos como al añadir la opción /i no se distinguen mayúsculas de minúsculas mientras que si la omitimos hacemos distinción.


Referencias

echo "esto si es asi"|sed "s/\(.*es\)\(.*\)/\2 \1 /g"
 asi esto si es
echo "esto si es asi"|sed "s/\(.*es\)(.*\)/## & ##/g"
## esto si es asi ##

Los dos ejemplos anteriores utilizan parte o todo el valor buscado en la propia sustitución. Cada paréntesis especifica una subexpresión regular que posteriormente podremos usar en la sustitución llamándola como \1, \2,... según el orden de los paréntesis. Entonces la parte de (.*es) corresponde con \1 y (.*) corresponde con \2. Es decir, hemos cambiado de posición un texto. En el segundo ejemplo vemos comoel carácter "&" se utiliza en la sustitución con el valor de toda la línea.

Ejemplo:

Con Perl y expresiones regulares podemos

$ echo "esto si es asi"|perl -p -e  "s/(.*?es)(.*)/\2 \1/g"
to si es asi es
$ echo "esto si es asi"|perl -p -e  "s/(.*es)(.*)/\2 \1/g"

asi esto si es

 

Existen varios métodos de búsqueda

/i evita la distinción entre mayúsculas y minúsculas.

/s activa modo "línea única". En este modo los saltos de líneas verifican el punto.

/m activa modo "multilínea". En este modo los saltos de líneas verifican el $ y el carácter siguiente el ^.

/g global, hace referencia a todas las apariciones de la expresión regular. Si se omite sólo hacemos referencia a la primera.


Ejemplos

Expresión para comprobar una dirección de correo electrónico:

"^([0-9a-zA-Z]+)([._-]([0-9a-zA-Z]+))*@([0-9a-zA-Z]+)([._-]([0-9a-zA-Z]+))*[.]([0-9a-zA-Z]){2}([0-9a-zA-Z])?$"

Expresión de un hiperenlace

"<a[ ]{1,}href=[\"']{0,}([a-zA-Z0-9/:~._#]{0,})[\"']{0,}[^>]{0,}>([^<]{0,})</a>"

Referencias

Cuando utilizamos subexpresiones, podemos utilizar el valor encontrado correspondiente a la subexpresión posteriormente, normalmente cuando realizamos sustituciones en cadenas de texto. Las subexpresiones se ordenan de izquierda a derecha correspondiendo \1 a la primera subexpresión, \2 a la segunda y así sucesivamente.

Por ejemplo, si la expresión regular es:

([A-Za-z]+)([0-9]+)

entonces \1 se refiere al conjunto de letras que han verificado la expresión regular (primer paréntesis) y \2 a una serie de dígitos.

Además el carácter "&" como referencia equivale a toda la expresión regular.

Construyendo expresiones regulares: ejemplos


Sintaxis de fecha del tipo dd/mm/aa

En este ejemplo sólo pretendemos comprobar que la sintaxis de la fecha es correcta, es decir que estábien construida independientemente de que la fecha sea realmente válida.

En una primera aproximación sólo comprobaremos que tenemos seis dígitos separados por "/".

/[0-9]{2}\/[0-9]{2}[0-9]{2}\/[0-9]{2}/

Usamos como separador de la expresión regular el carácter "/". Entonces los caracteres "/" propios del formato de fecha tienen que ir protegidos mediante la contrabarra ("\"). El resto de la expresión consiste en indicar que tenemos dos dígitos.

Esta expresión no verificará fechas del tipo 2/3/07, ya que según la definición obligamos a tener dos dígitos por componente. Para mejorarla exigiremos que los componentes correspondientes al dia y al mes puedan tener uno o dos dígitos:

/[0-9]{1,2}\/[0-9]{1,2}[0-9]{2}\/[0-9]{2}/

No obstante seguirían siendo valores de fecha aceptados 9/99/07. Para afinar un poco más la expresión tendríamos que exigir que el mes no pudiera ser mayor que 12 y el día no mayor que 31:

En el caso del mes pondríamos

\/([1-9]|1[012])\/

es decir o tenenemos un solo dígito que va del 1 al 9 o tenemos dos, de forma que el primer es un 1 fijo y el segundo puede ser 0, 1 ó 2.

En el caso del día, y con una razonamiento similar pondríamos:

\/([1-9]|[12][0-9]|3[01])\/

es decir el día es un solo dígito del 0 al 9, o dos dígitos donde el primero es 1 ó 2 y el segundo cualquiera entre el 0 y el 9 y por último un dígito que puede ser 3 y seguido de un 1 o un 2.

Ahora todo junto quedaría como

/\/([1-9]|[12][0-9]|3[01])\/\/([1-9]|1[012])\/[0-9]{2}/

Es importante observar como el carácter "|" permite seleccionar alternativas de concordancia.

Por último, si quisiéramos que también fuer válida una fecha como 01/02/08, la expresión quedaría como:

/\/(0?[1-9]|[12][0-9]|3[01])\/\/(0?[1-9]|1[012])\/[0-9]{2}/

Es decir, añadiendo "0?" estamos diciendo que el "0" puede o no estar presente.