Archivo

Entradas Etiquetadas ‘regular’

Sustituyendo texto con expresiones regulares en EMACS

Domingo, 18 de Julio de 2010 Gaspar Fernández Sin comentarios

regular_expression

Una de las herramientas más utilizadas (por mí al menos) es la de reemplazar texto. En EMACS la podemos encontrar con:

M-x replace-string

Con esta orden podemos cambiar un texto por otro dentro de un buffer o una selección. Hasta aquí bien. Pero alguna vez nos podemos encontrar con un texto que debemos reemplazar por otro, y aunque no es exactamente igual en todos los reemplazos que tenemos que hacer sigue una cierta lógica.

Imaginemos que tenemos este código en PHP (Gracias Antonio):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
switch ($pregunta) {
    case 0: $preg="<br><br>¿Qué significa PHP?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. Power H Processor</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. PHP: Hypertext Preprocessor</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. Para Hacer Poco</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. PHP: Harvard Preprocessor</a><br>";
            break;

    case 1: $preg="<br><br>¿Dónde se ejecuta PHP?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. En el Cliente</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. En algún lugar de la Mancha...</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. En el Servidor</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. Ninguna de las anteriores</a><br>";
            break;

    case 2: $preg="<br><br>¿Cuál de las siguientes variables NO existe como predefinida en PHP?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. PTTS_BOOK</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. PHPSESSID</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. REQUEST_METHOD</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. REMOTE_ADDR</a><br>";
            break;

    case 3: $preg="<br><br>¿Quién creó PHP?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. Rasmus Lerdorf</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. Obi One Kenobi</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. Bill Gates</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. Andrew S. Tanenbaum</a><br>";
            break;

    case 4: $preg="<br><br>¿Con qué <i>marca</i> habrá un salto de línea en el código fuente?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. \\t</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. \\n</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. &lt;br&gt;</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. \\g</a><br>";
            break;

    case 5: $preg="<br><br>¿Cuál de las siguientes funciones NO se puede acceder al servidor de datos MySQL?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. mysql_query</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. mysql_connect</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. mssql_connect</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. mysql_pconnect</a><br>";
            break;

    case 6: $preg="<br><br>¿Cuál es la frase correcta?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. setcookie() puede ser llamada desde cualquier parte del documento HTML</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. Las cookies sólo se pueden mandar antes de mandar cualquier otra cabecera</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. Las cookies no son parte de la cabecera HTTP</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. El perro de San Roque no tiene rabo, porque el Chiquito se lo ha robado</a><br>";
            break;

    case 7: $preg="<br><br>¿La función ftp_get?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. Sube un fichero al servidor FTP</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. Establece una conexión FTP</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. Descarga un fichero del servidor FTP</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. Devuelve el nombre del directorio actual</a><br>";
            break;
}

Y queremos sustituirlo por este otro:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
addPregunta($pregresp, "¿Qué significa PHP?", "a. Power H Processor", "b. PHP: Hypertext Preprocessor", "c. Para Hacer Poco", "d. PHP: Harvard Preprocessor");

    addPregunta($pregresp, "¿Dónde se ejecuta PHP?", "a. En el Cliente", "b. En algún lugar de la Mancha...", "c. En el Servidor", "d. Ninguna de las anteriores");

    addPregunta($pregresp, "¿Cuál de las siguientes variables NO existe como predefinida en PHP?", "a. PTTS_BOOK", "b. PHPSESSID", "c. REQUEST_METHOD", "d. REMOTE_ADDR");

    addPregunta($pregresp, "¿Quién creó PHP?", "a. Rasmus Lerdorf", "b. Obi One Kenobi", "c. Bill Gates", "d. Andrew S. Tanenbaum");

    addPregunta($pregresp, "¿Con qué <i>marca</i> habrá un salto de línea en el código fuente?", "a. \\t", "b. \\n", "c. &lt;br&gt;", "d. \\g");

    addPregunta($pregresp, "¿Cuál de las siguientes funciones NO se puede acceder al servidor de datos MySQL?", "a. mysql_query", "b. mysql_connect", "c. mssql_connect", "d. mysql_pconnect");

    addPregunta($pregresp, "¿Cuál es la frase correcta?", "a. setcookie() puede ser llamada desde cualquier parte del documento HTML", "b. Las cookies sólo se pueden mandar antes de mandar cualquier otra cabecera", "c. Las cookies no son parte de la cabecera HTTP", "d. El perro de San Roque no tiene rabo, porque el Chiquito se lo ha robado");

    addPregunta($pregresp, "¿La función ftp_get?", "a. Sube un fichero al servidor FTP", "b. Establece una conexión FTP", "c. Descarga un fichero del servidor FTP", "d. Devuelve el nombre del directorio actual");

    addPregunta($pregresp, "¿Con qué función se puede eliminar una variable de sesión?", "a. session_unregister", "b. session_destroy", "c. session_register", "d. jam_session");

    addPregunta($pregresp, "¿Con que harías un buen bocadillo...?", "a. Con pavo de ese que tiene trozos de colores", "b. Con un milagro", "c. Con habichuelas", "d. Con las manos");

El switch podemos quitarlo a mano, y en este ejemplo, tal vez para 7 preguntas, podríamos hacerlo a mano, aunque si fuera alguna más, nos pensaríamos este nuevo método.

Bien, en EMACS, tenemos la siguiente herramienta:

M-x regexp-builder

Que nos seleccionará el texto que corresponde con la expresión regular que estemos construyendo, y nos ayudará a generar una expresión más compleja, en definitiva queremos que la expresión cubra todos los cases, para continuar la lectura del post, nos fijamos en el primero:

1
2
3
4
5
6
case 0: $preg="<br><br>¿Qué significa PHP?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. Power H Processor</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. PHP: Hypertext Preprocessor</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. Para Hacer Poco</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. PHP: Harvard Preprocessor</a><br>";
            break;

Para construir la expresión, hay mucho copy-paste, además, desde Linux podemos seleccionar texto y pegarlo con el botón central del ratón, lo que nos facilita mucho la vida. Por otra parte, hay que tener en cuenta que tenemos que escapar muchos caracteres (como “, [, (, ), ], ?), de todas formas, nos vamos dando cuenta de eso al construir la expresión, si EMACS no selecciona un carácter como correspondiente a la expresión, lo más normal es que tengamos que escaparlo, también, tenemos que tener en cuenta que un espacio en blanco se representa aquí como [:space:]. Con todo esto, construimos la siguiente expresión:

“case [0-9]: $preg=\”

[a-zA-Z_.\<\>\/\¿\?áéíóú[:space:]]*
\”;
[[:space:]]*$resp=\”[\\_()a-zA-Z,\:áéíóú&;[:space:]\.]+
\”.
[[:space:]]*\”[\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+
\”.
[[:space:]]*\”[\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+
\”.
[[:space:]]*\”[\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+
\”.
[[:space:]]*break;

Se seleccionarán todos los case, ahora copiamos la expresión para luego pegarla para reemplazarla. Aunque antes, tenemos que fijarnos en una cosa, en cada línea que queremos sustituir hay fragmentos de texto que queremos que figuren en la cadena sustituida, queremos hacerlo también automáticamente, eso lo conseguimos poniendo entre paréntesis en la expresión anterior las cadenas que vamos a aprovechar del texto que vamos a sustituir. La dejamos así:

case [0-9]: $preg=\”

\([a-zA-Z_.\<\>\/\¿\?áéíóú[:space:]]*\)
\”;
[[:space:]]*$resp=\”\([\\_()a-zA-Z,\:áéíóú&;[:space:]\.]+\)
\”.
[[:space:]]*\”\([\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+\)
\”.
[[:space:]]*\”\([\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+\)
\”.
[[:space:]]*\”\([\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+\)
\”.
[[:space:]]*break;

Ahora llamamos a la siguiente orden:

M-x replace-regexp

Introducimos como expresión la última que hemos hecho (la de los paréntesis, marqué en negrita los textos que queremos conservar). Y la sustituimos por lo siguiente:

addPregunta($pregresp, “\1″, “\2″, “\3″, “\4″, “\5″);

Vemos que es una función a la que le pasamos una variable y después cinco textos, el primer texto (\1), coincidirá con la pregunta (en el código de los cases), y desde el segundo (\2) hasta el quinto (\5) coincidirá con cada una de las cuatro posibles respuestas.

Una vez hecho esto se sustituirán todo el código de los cases, por el de la función addPregunta() con los textos correspondientes.

Tal vez este ejemplo haya sido un poco grande, pero las expresiones regulares son muy útiles, seguro que casi a diario encontráis la necesidad de hacer algo parecido a esto en vuestro editor de texto.

Referencias:
Text Pattern Matching in EMACS
Regexp Replace - GNU Emacs

Foto: fdecomite (Flickr)

Sed… de venganza (1): Sustituyendo cadenas en múltiples archivos

Lunes, 28 de Junio de 2010 Gaspar Fernández 1 comentario

3846929292_60721fb24e

Es uno de los grandes desconocidos y tan temidos comandos de que disponemos. Y es cierto que a veces da pereza mirarse el manual cuando queremos hacer algo que sed podría hacer rápidamente.

Lo que cuento hoy es su uso más popular (porque sed se puede usar para muuuuuchas cosas) y es muy simple, sustituir en un stream un texto por otro (Donde dije digo, digo Diego).

Imaginemos un fichero de texto, para ser originales llamémosle README, y en el texto queremos cambiar la palabra “Ireland” por “Spain”. Podemos hacer lo siguiente:

$ sed ’s/Ireland/Spain/g’ README

y veremos en pantalla el texto. Para guardarlo en el mismo archivo, si buscamos por Internet, veremos cómo la gente se complica la vida (que no digo que sea malo, yo también me la complico un poco más abajo), pero podemos usar el modificador -i (que tiene algunas funciones curiosas).

Si ahora hacemos:

$ sed -i ’s/Ireland/Spain/g’ README

Los cambios se guardarán automáticamente en el fichero README. Pero si investigamos un poco más, y queremos rematar la faena podemos hacer:

$ sed -i~ ’s/Ireland/Spain/g’ README

En este caso, guardaremos el cambio en el fichero README y grabaremos en README~ una copia de seguridad del fichero antiguo. La extensión de la copia de seguridad podemos cambiarla haciendo por ejemplo:

$ sed -i.bak ’s/Ireland/Spain/g’ README

Para que la copia sea README.bak

Pero ahora viene algo interesante, con el parámetro -i podemos modificar todos los archivos que queramos, sed acepta en la entrada múltiples archivos, por lo que si en un directorio con muchos archivos queremos cambiar un texto (imaginad que habéis hecho un proyecto relativamente grande, y hay una función con un nombre un poco ridículo, curiosamente es la que más veces llamáis, y como vamos a enseñar el código fuente, no queremos que nadie lea eso), podremos hacer:

$ sed -i ’s/nombre_ridiculo/nombre_elegante/g’ *

Ahora bien, si el proyecto está en múltiples directorios, siempre podemos usar find para localizar los archivos de la siguiente forma:

find -name ‘*.c’ -exec sed -i ’s/nombre_ridiculo/nombre_elegante/g’ {} \;

Con toda esta línea, buscaremos todos los archivos con extensión .c dentro del directorio actual y subdirectorios y se los pasaremos a sed, con el modificador -exec de find, ejecutaremos el comando que especificamos, donde {} indica el nombre de archivo (que nos lo da find) y con \; indicamos el fin del comando y sus parametros.

Pero esto no termina aquí, sed soporta expresiones regulares, y si por ejemplo queremos coger todas las imágenes de un fichero html, cambiarlas de directorio y añadirles un class (inicialmente encontramos <img src=”foto.jpg” alt=”" /> , y queremos que salga <img class=”imagen” src=”/static/foto.jpg” alt=”" />) podemos hacer lo siguiente:

sed ’s/src=”\(.[a-zA-Z\.\_\/]*\)”/src=”\/static\/\1” class=”imagen”/g’ fichero.html

Lo que hay en negrita, corresponde a la expresión regular que determina el nombre del archivo (caracteres de la a a la z, de la A a la Z, puntos, guiones bajos, y barras (en la cadena de origen), en la cadena de destino escribimos \1 donde queremos que coloque el texto correspondiente a la expresión anterior, es decir donde queremos que coloque el nombre del archivo.

Ni que decir tiene que podemos hacer una mezcla de todo lo dicho en este post ( expresiones regulares, y sustitución en múltiples archivos dentro de múltiples subdirectorios y guardando backups ), y estaremos delante de una potente herramienta.

Lo malo de ejecutar sed, es que tenemos que escapar muchos caracteres,por lo menos ), (, ., \, / y seguro que encontramos alguno más; para ello, siempre que queramos introducir un carácter de esos, debemos poner una contra barra (\) delante.

Foto: albertopveiga (Flickr)

Visita otras webs de la red