Hace tiempo que tenía ganas de escribir una entrada sobre expresiones regulares en PHP, que una vez controladas son realmente útiles. Para ir viendo cómo funcionan emplearé la función preg_replace($patron, $reemplazo, $cadena), que reemplaza las coincidencias de un patrón, es decir, de una expresión regular, en una cadena.
Así, por ejemplo, reemplazaríamos todas las aes de la cadena por una R.
$cadena = "Soy una cadena con 123 números y guiones <>";
$patron = "/a/";
$reemplazo = "R";
echo preg_replace($patron, $reemplazo, $cadena);
// esto da como resultado: Soy unR cRdenR con 123 números y guiones <>
Delimitadores de patrón
El patrón, en inglés pattern, debe estar delimitado por unos caracteres especiales que le indiquen a PHP donde empieza y donde termina. Se puede emplear «cualquier carácter no alfanumérico, que no sea una barra invertida, y que no sea un espacio en blanco», pero lo más frecuente es emplear una barra inclinada //. Lo que esté dentro de estos dos signos es el «patrón», es decir, los caracteres que deben coincidir (macht, en argot).
Si necesitamos que en patrón haya precisamente una barra, se puede «escapar» con la barra invertida. (Se entiende por escapar que PHP no debe leer las características especiales de ese signo, sino tratarlo como uno más).
$cadena = "Soy una cadena con /barras/";
$patron = "/\//";
$reemplazo = "R";
echo preg_replace($patron, $reemplazo, $cadena);
// Resultado: Soy una cadena con RbarrasR
Modificadores del delimitador
La manera de comportarse del patrón puede modificarse añadiendo después del delimitador final determinados caracteres. Un ejemplo sencillo: los patrones son por lo general case sensitive, es decir, que se diferencia entre mayúsculas y minúsculas. Por ejemplo, de esta manera solo coincidirían las aes mayúsculas.
$cadena = "Iba Antonio a América a andar a gatas";
$patron = "/A/";
$reemplazo = "R";
echo preg_replace($patron, $reemplazo, $cadena);
// Resultado: Iba Rntonio a Rmérica a andar a gatas
Pero añadiendo el modificador i (PCRE_CASELESS) al delimitador del patrón podemos forzarlo para que se comporte de manera case insensitive.
$patron = "/A/i";
// Resultado: IbR Rntonio R RméricR R RndRr R gRtRs
Metacaracteres
La gracia de los patrones es que permiten incluir caracteres comodín, metacaracteres en phpesiano, que permiten afinar la búsqueda. Por ejemplo, el acento circunflejo ^ indica que la coincidencia debe encontrarse al principio de la cadena. Así, en este caso, no se produciría ningún reemplazo, ya que la cadena empieza por S.
$cadena = "Soy una cadena con 123 números y guiones <>";
$cadena = "Soy una cadena antigua";
$patron = "/^a/";
$reemplazo = "R";
echo preg_replace($patron, $reemplazo, $cadena);
En cambio así quitaríamos la S.
$patron = "/^S/";
// Resultado: Roy una cadena antigua
Lo mismo sucede con el símbolo de dólar o peso $ para indicar que debe estar al final de la cadena. Fijaos que en este caso el carácter a encontrar va antes del patrón.
$cadena = "Soy una cadena en paz";
$patron = "/z$/";
// Resultado: Soy una cadena en paR
Los metacaracteres principales son:
Carácter | Coincidencia | Cadena | Resultado |
---|---|---|---|
. (/./) | Cualquier resultado | abcd12345 | RRRRRRRRR |
^ (/^a/) | Principio de la cadena | abcd12345 | Rbcd12345 |
$ (/5$/) | Final de la cadena | abcd12345 | abcd1234R |
\s (/\s/) | Espacios en blanco | ab 345 | abR345 |
\d (/\d/) | Cualquier número | abcd12345 | abcdRRRRR |
\D (/\D/) | Cualquier letra | abcd12345 | RRRR12345 |
\w (/\w/) | Cualquier letra, número o guion bajo | a*d_12^!5 | R*RRRR^!R |
\W (/\W/) | Cualquier carácter que no sea una letra, un número o o el guion bajo | a*d_12^!5 | aRd_12RR5 |
Cuantificadores
Mediante el uso de llaves podemos indicar cuántas veces debe aparecer como mínimo y máximo el patrón. Las llaves se escriben después del match y, con el primer número, se indica el mínimo de veces que debe aparecer y, en el segundo, el máximo. Por ejemplo, esto:
$cadena = "a aaa aaa aaaa";
$patron = "/a{2,3}/";
$reemplazo = "R";
Daría como resultado: a R R Ra. Es decir, en el primer grupo de aes no encuentra nada, pues debe hacer como mínimo 2; y en el cuarto solo reemplaza tres, pues debe haber como máximo 3.
Si no se indica el segundo número y no se pone la coma, el macht debe aparecer exactamente ese número de veces.
$patron = "/a{2}/";
// Resultado: a Ra Ra RR
Y si se pone la coma, pero no el segundo número, se entiende que el máximo es ilimitado (bueno, inferior a 65536, que para el caso es lo mismo).
$patron = "/a{2}/";
// Resultado: a Ra Ra RR
Existen tres caracteres comodines para definir cuantificadores: *, + y ?
- *: equivale a {0,}
- +: equivale a {1,}
- ?: equivale a {0,1}
Por ejemplo, así nos cambiaría todas las aes porque al menos se encuentran 1 vez:
$cadena = "a aaa aaa aaaa";
$patron = "/a+/";
// Resultado: R R R R
Un poco más adelante comprenderemos su utilidad con algunos ejemplos prácticos, pero sigamos ahora con la teoría para conocer los subpatrones ^^.
Subpatrones
Mediante corchetes [] y paréntesis () podemos crear subpatrones, que sirven para encontrar expresiones regulares dentro de otras. Los corchetes se para comparar rangos. Por ejemplo, con el rango a-z encontraríamos cualquier carácter en minúscula comprendido entre la a y la z. Así, en este patrón no encontraríamos ni la primera e, pues está precedida por una T mayúsucula, ni la segunda, que está precedida por un número.
$cadena = "Tengo un mero somero y tú una pera llamada 1e";
$patron = "/[a-z]e/";
// Resultado: Tengo un Rro soRro y tú una Rra llamada 1e
Si el rango va precedido por un acento circunflejo se produce justo el efecto contrario, es decir, se seleccionan las coincidencias (letra e precedida por cualquier carácter) que no cumplen la condición.
$patron = "/[^a-z]e/";
// Resultado: Rngo un mero somero y tú una pera llamada R
Rangos habituales:
- [a-z]: cualquier carácter en minúscula.
- [A-Z]: cualquier carácter en mayúscula.
- [0-9]: cualquier número.
- [[:alpha:]]: cualquier carácter del alfabeto.
- [[:digit:]]: cualquier número.
- [[:punct:]]: cualquier carácter que no sean números y letras (como una exclamación ! o una coma ,).
- [[:space:]]: cualquier espacio en blanco.
- [[:upper:]]: cualquier mayúscula.
- [[:lower:]]: cualquier minúscula.
Los paréntesis sirven para crear subexpresiones y buscar alternativas mediante el operador |, que significa o (o esto o lo otro). Por ejemplo, esto capturaría ma, pero también ca.
$cadena = "Roma no cayó en un día";
$patron = "/(m|c)a/";
// Resultado: RoR no Ryó en un día
Un ejemplo
Para terminar este toxo-post vamos a ver un ejemplo que nos permita comprender las posibilidades de las expresiones regulares. Pero antes hablaré de otra función muy útil para estos casos: preg_match(), que busca una coincidencia en un patrón, en cuyo caso devuelve 1 (true, verdadero); de lo contrario, 0 (false). Y ahora al lío ^^.
Las expresiones son muy útiles para validar los datos de un formulario. Por ejemplo, sabemos que cualquier correo electrónico debe tener esta estructura:
cadena1@cadena2.cadena3
Es decir, debe cumplir estas condiciones:
- Antes de la arroba debe haber una cadena de texto.
- Luego tiene que haber una arroba.
- A continuación otra cadena de texto.
- Un punto.
- Y luego otra cadena que debe tener al menos dos caracteres.
¿Nos animamos a intentarlo antes de ver la solución?
Una solución posible sería esta:
$patron = "/([a-z]|[0-9])+@([a-z]|[0-9])+\.[a-z]{2,}/i";
if ( preg_match($patron, $cadena) == 0 ) {
echo "Correo inválido";
} else {
echo "Correo válido";
}
Cadenas que funcionarían con ese patrón:
- $cadena = "zutanito@gmail.com";
- $cadena = "zutaas2223ito@gmail.com";
Cadenas que no funcionarían con ese patrón:
- $cadena = "zutanitogmail.com";
- $cadena = "@gmail.com";
- $cadena = "zutanito@.com";
- $cadena = "zutanito@gmail.";
- $cadena = "zutanito@gmailcom";
Además de validar formularios, otra utilidad entre tantas de las expresiones regulares es conseguir URLs amigables, pero eso lo vemos en otro post que por hoy ya está bien. Para los impacientes, dos enlaces muy chulos: al blog de Juan Díaz Bustamante y a Desarrollo Web, dos páginas de consulta obligada para quien gusta de las cosas de Internet.
Epílogo: POSIX y PCRE
En PHP hay había dos maneras de trabajar con expresiones regulares. Una es siguiendo el modelo POSIX, acrónimo de Portable Operating System Interface, y otra el modelo PCRE, de Perl Compatible Regular Expressions, que es la que he seguido aquí. El modelo POSIX está obsoleto en PHP a partir de su versión 5.3.
Entre otras cuestiones, esto ha dejado invalidadas algunas funciones que no vale la pena ver aquí (las ereg... y split). En su lugar, hay que usar estas:
- preg_replace(): reemplaza un patrón
- preg_match(): encuentra un patrón
- preg_split(): selecciona un patrón.
Conclusión: las expresiones regulares son muy útiles y tienen la virtud de que no ocupan espacio en la memoria personal, pues a la media hora se te han olvidado por completo :P.
Adenda
Borja me da a conocer una página chulísima para construir expresiones regulares: http://gskinner.com/RegExr/
Excelente post,
me ha aclarado el tema de manera clara y amena.
Da gusto leer cosas así.
gracias!
Sin duda uno de los mejores articulo de introducción a las expresiones regulares que he leído! 🙂
Muchas gracias, Félix
saludos, como hago para conseguir un patron de este tipo: por ejemplo
GP-12345-12. esos 2 caracter, seguido de un guion, 5 digitos o mas otro guin y termina con 2 digitos o mas
Excelente explicación!! por fin entiendo esto del preg_replace de php
Buenas noches
Estoy leyendo su tutorial porque necesito remover con preg_replace los enlaces de un string.
Lo consigo con la siguiente linea:
preg_replace(‘/(.*?)/’, “\\2”, $limpiado1);
El problema es que algunos de los textos de los enlaces tienen un salto de linea, y entonces estos enlaces no son removidos. Y no entiendo el por qué.
Por ejemplo, quita las etiquetas <a href… escritas de esta forma:
Texto sin salto
Pero no quita los escritos así:
Texto con salto
Si pudieras echarme un cable, para quitar todos los enlaces, incluso los que en su texto tienen un salto de linea, t elo agradezco mucho, porque seguro que para alguien como tu es muy sencillo.
Lo siento, puse código y fue interpretado…
Excelente tutorial, gracias por el aporte.
Gracias Andrés.