El post que buscas se encuentra eliminado, pero este también te puede interesar

Con el Terminal: Uso de expresiones regulares II: Reemplazos

Anuncios

Muchos de los caracteres especiales de los que os he hablado en el artículo anterior se pueden combinar, no sólo con otros caracteres, sino con expresiones regulares enteras. La forma de hacer esto es usar paréntesis para formar una subexpresión. Vamos a ver un ejemplo de esto. Empecemos descargando un texto que nos sirva para hacer pruebas. Se trata de una lista de frases. Para eso vamos a usar el siguiente comando:

curl http://artigoo.com/lista-de-frases-comparativas-comicas 2>/dev/null | sed -n 's/.*(.*.)</p>/1/gp' > frases


Esto dejara en el directorio donde lo lanza un fichero con nombre “frases”. Podes abrirlo para echarle un vistazo y reír un poco.

Ahora vamos a suponer que queremos encontrar las frases que tengan exactamente 6 palabras. La dificultad está en formar una expresión regular que empareje con cada palabra. Una palabra es una secuencia de letras ya sean mayúsculas o minúsculas, lo que sería algo así como '[a-zA-Z]+', pero también hay que especificar que estas letras tienen que estar separadas por otros caracteres que no sean letras, o sea que sería algo como '[a-zA-Z]+[^a-zA-Z]+'. Recordemos: el “^” como primer carácter dentro de los corchetes indica que queremos emparejar con caracteres que no están en los rangos y el “+” indica 1 o más caracteres.

Ya tenemos una expresión regular que puede emparejar con una palabra. Para emparejarla con 6, habrá que repetirla 6 veces. Para eso usábamos las llaves, pero no sirve poner '[a-zA-Z]+[^a-zA-Z]+{6}', porque el 6 repetiría la última parte de la expresión regular y lo que queremos es repetirla toda, así que lo que hay que poner es esto: '([a-zA-Z]+[^a-zA-Z]+){6}'. Con los paréntesis formamos una subexpresión y con las llaves la repetimos 6 veces. Ya sólo falta añadir un “^” delante y un “$” detrás para emparejar con la linea entera. El comando es el siguiente:

grep -E '^([a-zA-Z]+[^a-zA-Z]+){6}$' frases

Y el resultado es justo el que queríamos:

Está más cantado que la Macarena.
Estás más acabado que Luis Aguilé.
Tienes menos cultura que una piedra.
Sabes más idiomas que Cañita Brava.
Tiene más arrugas que Tutan Khamón.
Sabes menos que Rambo de puericultura.


Miren en que ponemos el parámetro -E porque queremos usar expresiones regulares extendidas para que funcione el “+”. Si usásemos las básicas, habría que escapar los paréntesis y las llaves.

Referencias hacia atrás o backreferences

Si tenes instalado algún corrector ortográfico, probablemente tendrás una lista de palabras en /usr/share/dict/words. Si no es así, podes instalarla en arch con:

sudo pacman -S words


O en debian con:

sudo aptitude install dictionaries-common

Si queres podes echarle un vistazo al fichero para ver qué palabras tiene. En realidad es un enlace al fichero de palabras del idioma en el que esté nuestra distro. Se pueden tener varios ficheros de palabras instalados a la vez.

Vamos a usar ese fichero. Resulta que tenemos mucha curiosidad por saber todos los palíndromos de siete letras que hay. Para el que no lo sepa: Un palíndromo es una palabra capicúa, o sea, que se puede leer igual de izquierda a derecha que de derecha a izquierda. Probemos el siguiente comando:

grep '^(.)(.)(.).321$' /usr/share/dict/words

Tiene un aspecto un poco extraño, ¿verdad? Si lo probamos, el resultado dependerá del idioma de vuestra distro y de las palabras que haya en vuestra lista, pero en mi caso, con el idioma español, el resultado es este:

anilina
recocer
rodador


Vamos a ver cómo funciona esta expresión regular.

Aparte del “^” y el “$”, que ya sabemos para qué sirve, lo primero que vemos a la izquierda son tres grupos de puntos encerrados entre paréntesis. Que no os confundan las barras que hay delante de cada paréntesis. Son para escapar los paréntesis porque estamos usando expresiones regulares básicas, pero no tienen ningún otro significado. Lo importante es que estamos pidiendo con los puntos tres caracteres cualesquiera, pero cada uno de esos puntos están encerrados entre paréntesis. Esto sirve para que guarde los caracteres que encajan con esos puntos de manera que se pueda volver a hacer referencia a ellos desde la expresión regular. Este es otro uso de los paréntesis que será muy útil más adelante para hacer reemplazos.

Aquí es donde vienen los tres números que hay a continuación con la barra delante. En este caso, la barra sí es importante. Sirve para indicar que el número que hay a continuación es una backreference y está haciendo referencia a uno de los paréntesis anteriores. Por ejemplo: 1 hace referencia al primer paréntesis, 2 al segundo y así sucesivamente.

O sea que con la expresión regular que hemos puesto, lo que buscamos son todas las palabras que empiecen por cuatro letras cualesquiera y luego tengan una letra que sea igual que la tercera, otra que sea igual que la segunda y otra que sea igual que la primera. El resultado son los palíndromos de siete letras que estén en la lista de palabras. Tal como queríamos.

Si estuviéramos usando expresiones regulares extendidas, no habría que escapar los paréntesis, pero con expresiones regulares extendidas no funcionan las backreferences en todos los programas porque no están estandarizadas. Sin embargo, con grep funcionan, o sea que esa puede ser otra forma de hacer lo mismo. Podéis probarlo si queres.

Expresiones de reemplazo: El comando sed

Además de hacer búsquedas, una de las mejores utilidades de las expresiones regulares es reemplazar textos complejos. Para ello, una forma de hacerlo es con el comando sed. La potencia del comando sed va mucho más allá de reemplazar textos, pero aquí voy a utilizarlo para eso. La sintaxis que voy a usar con este comando es la siguiente:

sed [-r] 's/REGEX/REPL/g' FICHERO

O también:

COMANDO | sed [-r] 's/REGEX/REPL/g'

Donde REGEX será la expresión regular de búsqueda y REPL la de reemplazo. Tene en cuenta que este comando no reemplaza realmente nada en el fichero que le indiquemos, sino que lo que hace es mostrarnos el resultado del reemplazo en la terminal, así que no se asusten por los comandos que voy a poner a continuación. Ninguno de ellos va a modificar ningún fichero de nuestro sistema.

Empecemos con un ejemplo sencillo. Todos tenemos en el directorio /etc varios ficheros de configuración que, normalmente, tienen comentarios que empiezan por “#”. Supongamos que queremos ver uno de estos ficheros sin los comentarios. Por ejemplo, voy a hacerlo con el fstab. Podes probar con el que quieras

sed 's/#.*//g' /etc/fstab

No voy a poner aquí el resultado del comando porque depende de lo que tengas en nuestro fstab, pero si comparas la salida del comando con el contenido del fichero veras que todos los comentarios han desaparecido.

En este comando la expresión de búsqueda es “#.*“, o sea un “#” seguido de cualquier número de caracteres, o sea, los comentarios. Y la expresión de reemplazo, si te fijas en las dos barras seguidas, veras que no hay ninguna, así que lo que está haciendo es reemplazar los comentarios por nada, o sea, borrarlos. Más sencillo imposible.

Ahora vamos a hacer lo contrario. Supongamos que lo que queremos es comentar todas las líneas del fichero. Probemos así:

sed 's/^/# /g' /etc/fstab

Veras que, en la salida del comando, todas las líneas empiezan por una almohadilla y un espacio en blanco. Lo que hemos hecho es reemplazar los inicios de línea por “# “. Este también es un ejemplo bastante simple en el que el texto por el que se reemplaza es siempre el mismo, pero ahora vamos a complicarlo un poco más.

La gracia de los reemplazos está en que en la expresión de reemplazo se pueden utilizar backreferences como las que he contado antes. Volvamos al fichero de frases que nos hemos descargado al principio del artículo. Vamos a meter entre paréntesis todas las letras mayúsculas que haya, pero lo haremos con un comando:

sed 's/([A-Z])/(1)/g' frases

Lo que tenemos aquí es una backreference en la expresión de reemplazo que hace referencia al paréntesis que hay en la expresión de búsqueda. Los paréntesis que hay en la expresión de reemplazo son paréntesis normales. En la expresión de reemplazo no tienen ningún significado especial, se ponen tal cual. El resultado es que todas las letras mayúsculas se reemplazan por esa misma letra, sea cual sea, con paréntesis alrededor.

Hay otro carácter que también se puede usar en la expresión de reemplazo, es “&” y se reemplaza por todo el texto emparejado por la expresión de búsqueda. Un ejemplo con esto podría ser meter todas las frases del fichero entre comillas. Esto se puede conseguir con este comando:

sed 's/.*/"&"/g' frases

El funcionamiento de este comando es muy similar al anterior, sólo que ahora lo que reemplazamos es la línea entera por esa misma linea con comillas alrededor. Como estamos usando “&” no hace falta poner paréntesis

Algunos comandos útiles con expresiones regulares

A continuación os voy a dejar unos cuantos comandos que me parecen útiles o curiosos y que utilizan expresiones regulares. Con estos comandos se ve mucho mejor la utilidad de las expresiones regulares que con los ejemplos que os he puesto hasta ahora, pero me parecía importante explicar algo del funcionamiento de las expresiones regulares para poder entender estas.

Mostrar las secciones de una página del manual:

man bash | grep '^[A-Z][A-Z ]*$'

Por supuesto, podes cambiar el comando bash por el que queras. Y luego desde man, podes ir directamente a la sección que os interesa usando, como no, una expresión regular. Pulsas “/” para empezar a buscar y escribís “^ALIASES$” para ir a la sección ALIASES, por ejemplo. Creo que este es el primer uso que empece a hacer de las expresiones regulares hace ya unos cuantos años. Moverse por algunas páginas del manual es casi imposible si no se usa algún truco como este.

Mostrar los nombres de todos los usuarios de la máquina incluidos los especiales:

sed 's/([^: ]*).*/1/' /etc/passwd

Mostrar los nombres de los usuarios, pero sólo los que tienen shell:

grep -vE '(/false|/nologin)$' /etc/passwd | sed 's/([^: ]*).*/1/g'

Realmente se puede hacer con una sola expresión regular, pero la forma de hacerlo va más allá de lo que os he contado en estos artículos, así que lo he hecho combinando dos comandos.
Insertar una coma delante de las tres últimas cifras de todos los números que haya en el fichero numbers:

sed 's/(^|[^0-9.])([0-9]+)([0-9]{3})/12,3/g' numbers

Sólo funciona con números de hasta 6 dígitos, pero se puede lanzar más de una vez para colocar separadores en los demás grupos de tres cifras.
Extraer todas las direcciones de email de un fichero:

grep -E '<[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,4}>' FICHERO

Separar el día, mes y año de todas las fechas que aparezcan en un fichero:

sed -r 's/([0-9]{2})[/-]([0-9]{2})[/-]([0-9]{4})/Día: 1, Mes: 2, Año: 3/g' FICHERO


Averiguar nuestra IP local:

/sbin/ifconfig | grep 'inet .*broadcast' | sed -r 's/[^0-9]*(([0-9]+.){3}[0-9]+).*/1/g'

Esto también se puede hacer con un sólo comando sed, pero mejor lo separo en un grep y un sed para simplificarlo.

Anuncios

4 comentarios - Con el Terminal: Uso de expresiones regulares II: Reemplazos

@Mondongo_Peludo +5
Por fin un post en esta categoría, ya me tienen la bola por el suelo con steam.
@oaker +1
Muy util.