¡Hola, encargados del scripting!Arqueamiento de cejas ante expresiones regulares

Los encargados del scripting

Descargar el código de este artículo: HeyScriptingGuy2008_05.exe (150KB)

En noviembre del pasado año 2007, los encargados del scripting pasaron un día en París mientras se dirigían a la conferencia del Tech•Ed IT Forum celebrado en Barcelona. Durante esta breve parada de un día, tuvimos la oportunidad de visitar el Louvre, uno de los museos de arte más conocidos a nivel mundial.

Y, ¿cómo encontraron los encargados del scripting el museo Louvre? Muy fácil: subimos hacia Notre Dame y después torcimos a la izquierda.

¡Ah! ¿Se refiere a que si nos gustó el Louvre? En su mayor parte, sí. El único problema que tuvimos es que el Louvre, como la mayoría de los museos, aplica la política de "se mira pero no se toca". Todos sabemos que la Mona Lisa sería más atractiva aún si tuviera cejas, pero, por alguna razón en particular, los encargados de cuidar el museo se molestan muchísimo si se intenta arreglar cualquiera de sus cuadros.

Nota: en realidad, a los dos encargados del scripting les gustó la Mona Lisa. Lo cual fue una sorpresa muy agradable, puesto que después de todo el revuelo organizado en torno a esta obra, nos temíamos que fuera simplemente otra obra pictórica más. Afortunadamente, no fue así, era realmente hermosa. (Si bien, podía tener unas pequeñas cejas al menos). Sin embargo, sorprendentemente, a los dos nos desilusionó la igualmente archiconocida Venus de Milo. Ninguno de nosotros consideramos su ejecución como algo espectacular, es más, el encargado del scripting que escribe esta columna se quedó perplejo al conocer el concepto artístico de la Venus de Milo. ¿La estatua de una mujer que no tiene brazos? ¿Cómo se supone, entonces, que puede barrer o fregar los platos...?

Nota dedicada a nuestras lectoras (suponiendo que todavía quede alguna por ahí...): obviamente, se trata de un error tipográfico. Lo que realmente queríamos decir era esto: ¿Una estatua de una mujer que no tiene brazos? Y a pesar de todo, puede hacer el doble de trabajo que cualquier hombre y hacerlo bien.

Los encargados del scripting se disculpan por cualquier equívoco que nuestra frase original haya podido motivar.

En cualquier caso, mientras que sólo teníamos ojos para los divinos tesoros que se hospedan en el Louvre, los dos encargados del scripting sólo podían pensar en una cosa: ¿Dónde están los baños? Mientras los buscaban, el encargado del scripting que escribe esta columna pensó en otra cosa: los encargados del scripting somos unos hipócritas. Después de todo, nos ha molestado mucho que el Louvre no nos permita arreglar la Mona Lisa. Y aún así, somos culpables de un pecado semejante. En el número de enero de 2008 de TechNet Magazine, escribimos un artículo acerca del uso de expresiones regulares en un script. Éste podría considerarse un perfecto ejemplo de "se mira pero no se toca": le mostramos cómo usar las expresiones regulares para identificar los problemas presentes en un archivo de texto, pero no le mostramos cómo arreglar esos problemas. ¡Zut alors!

Nota: así pues, si los encargados del scripting fueron al Louvre en noviembre de 2007, ¿cómo es posible que el encargado del scripting que escribe esta columna haya recordado de repente datos concretos de un artículo que no apareció en TechNet Magazine hasta enero de 2008? Vaya, esto sí que es un acertijo, ¿verdad? Debe tener algo que ver con las diferencias horarias entre Redmond y París.

No obstante, afortunadamente, y a diferencia de los encargados del museo Louvre, los encargados del scripting están dispuestos a admitir cuándo han cometido un error. Nos equivocamos al mostrarle únicamente cómo buscar información mediante expresiones regulares; deberíamos haberle mostrado cómo reemplazar información mediante expresiones regulares. De hecho, deberíamos haberle mostrado un script como el de la Figura 1.

Figure 1 Búsqueda y reemplazo

      Set objRegEx = _
    CreateObject("VBScript.RegExp")

objRegEx.Global = True   
objRegEx.IgnoreCase = True
objRegEx.Pattern = "Mona Lisa"

strSearchString = _
    "The Mona Lisa is in the Louvre."
strNewString = _
    objRegEx.Replace(strSearchString, _
                     "La Gioconda")

Wscript.Echo strNewString 

Ahora bien, para ser sinceros, éste es un uso bastante mundano de las expresiones regulares: lo que vamos a hacer aquí es reemplazar todas las instancias del valor de cadena Mona Lisa por La Gioconda (correspondiente en italiano de "Pero, ¿dónde diablos he puesto esas cejas?"). Sin duda alguna, podíamos haber llevado a cabo este reemplazo de una forma mucho más sencilla simplemente usando la función Replace de VBScript. Aunque me temo que no: usaremos este pequeño y sencillo script para explicar cómo llevar a cabo operaciones de búsqueda y reemplazo mediante expresiones regulares y, a continuación, una vez finalizado este paso, le mostraremos algunas de las tareas más atractivas que puede llevar a cabo con estas expresiones.

Como puede ver, este script no es muy complicado. Comenzamos creando una instancia del objeto VBScript.RegExp; no es necesario decir que se trata del objeto que nos permite usar las expresiones regulares dentro de un script de VBScript. Tras crear el objeto, asignamos, a continuación, valores a tres de las propiedades del objeto:

Global Al configurar esta propiedad en True, le indicamos al script que busque (y reemplace) cada una de las instancias de Mona Lisa que encuentra en el texto de destino. Si la propiedad Global fuese False (el valor predeterminado), el script buscaría y reemplazaría sólo la primera instancia de Mona Lisa.

IgnoreCase La configuración de IgnoreCase en True indica al script que deseamos llevar a cabo una búsqueda que no distinga entre mayúsculas y minúsculas, en otras palabras, mona lisa y Mona Lisa se considerarían idénticas. De forma predeterminada, VBScript realiza una búsqueda que distingue entre mayúsculas y minúsculas, lo cual significa que, gracias a las letras en mayúscula y en minúscula, mona lisa y Mona Lisa se consideran como valores completamente diferentes.

Pattern La propiedad Pattern contiene el valor que estamos buscando. En este caso, buscamos un sencillo valor de cadena: Mona Lisa.

A continuación, asignamos el texto que deseamos buscar a una variable denominada strSearchString:

strSearchString = "The Mona Lisa is in the Louvre."

Después, llamamos al método de expresión regular Replace y pasamos dos parámetros al método: el texto de destino que deseamos buscar (la variable strSearchString) y el texto de reemplazo (La Gioconda). Esto es lo que hemos hecho aquí:

strNewString = objRegEx.Replace(strSearchString, "La Gioconda")

Y eso es todo. El texto modificado se almacena en la variable strNewString. Si ahora devolvemos el valor de strNewString, debemos obtener lo siguiente:

The La Gioconda is in the Louvre.

La gramática puede ser un poco dudosa, pero seguro que ha captado la idea general.

Tal como hemos explicado anteriormente, esto está muy bien y es correcto, pero es definitivamente excesivo, podíamos haber conseguido lo mismo usando estas líneas de código (de hecho, podíamos haber hecho todo en una sola línea si lo hubiéramos deseado):

strSearchString = "The Mona Lisa is in the Louvre."
strNewString = Replace(strSearchString, "Mona Lisa", "La Gioconda")
Wscript.Echo strNewString

Es decir, veamos si podemos hacer algo interesante con las expresiones regulares que no podamos hacer con la función Replace de VBScript.

¿A nadie se le ocurre alguna idea? Bien, ésta es una. A menudo, nosotros, los encargados del scripting, terminamos teniendo que copiar texto de un tipo de documento a otro. A veces, esto funciona bastante bien, pero otras no. Cuando esto no funciona, aparecen problemas bastante curiosos con el espacio entre palabras, problemas que provocan que el texto tenga el siguiente aspecto:

Myer Ken, Vice President, Sales and Services

¡Vaya! Mire todos esos espacios en blanco tan extraños. Y, en este caso, la función Replace es de uso limitado. ¿Por qué? Bien, tenemos un número aparentemente aleatorio de irrelevantes espacios en blanco: puede que haya 7 espacios en blanco entre palabras, puede que haya 2 espacios en blanco entre palabras o puede que haya 6 espacios en blanco entre palabras. Esto dificulta la solución del problema mediante Replace. Por ejemplo, si tratamos de buscar 2 espacios en blanco consecutivos (reemplazando estos 2 espacios por un solo espacio en blanco), éste es el resultado:

Myer Ken, Vice President, Sales and  Services

Está mejor, pero tampoco mucho. Existe una manera de hacer esto, pero es necesario que busquemos un número arbitrario de espacios en blanco (digamos, 39); llevar a cabo el reemplazo; restar 1 al número inicial; buscar 38 espacios en blanco y realizar el reemplazo; y así progresivamente. De manera alternativa, podríamos usar este script de expresiones regulares bastante sencillo (y de carácter mucho más infalible):

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = " {2,}"

strSearchString = _
"Myer Ken, Vice President, Sales and Services"
strNewString = objRegEx.Replace(strSearchString," ")

Wscript.Echo strNewString

La clave de este script (y la clave de la mayoría de expresiones regulares) es la propiedad Pattern:

objRegEx.Pattern = " {2,}"

Lo que vamos a hacer aquí es buscar 2 (o más) espacios en blanco consecutivos. ¿Cómo sabemos que Pattern busca 2 espacios en blanco (o más)? Bien, dentro de nuestras comillas dobles tenemos un único espacio en blanco seguido por esta construcción: {2,}. En la sintaxis de expresiones regulares, esto indica que se busquen, al menos, 2 instancias consecutivas del carácter anterior (en este caso, un espacio en blanco). ¿Y qué ocurre si hay 3 ó 4 ó 937 espacios en blanco consecutivos? No pasa nada, podría tomar todos estos también. (Si, por alguna razón, deseamos incluir, al menos, 2 espacios en blanco pero no más de 8, usaríamos la sintaxis {2,8}, donde el 8 especifica el número máximo de coincidencias).

En otras palabras, siempre que encontremos 2 o más espacios en blanco, uno detrás de otro, vamos a tomar todos esos espacios consecutivos y los vamos a reemplazar con un único espacio en blanco. ¿Qué repercusión tendrá esto en nuestro valor de cadena original, el que tenía todos esos espacios en blanco irrelevantes? Éste.

Myer Ken, Vice President, Sales and Services

¿Lo ve? Los encargados del scripting realmente pueden mejorar y facilitar las cosas. Ojalá los empleados del Louvre nos permitieran ahora arreglar la Mona Lisa.

Le presentamos un escenario no por interesante menos frecuente. Suponga que su compañía cuenta con un directorio de teléfonos y que todos los números de teléfono tienen el siguiente formato:

555-123-4567

Sin embargo, su jefe ha decidido ahora que todos los números de teléfono deben tener el siguiente formato:

(555) 123-4567

¿Cómo podría cambiar el formato de todos esos números de teléfono? Bien, si fuéramos tan atrevidos, nos gustaría sugerirle que usara un script similar a éste:

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "(\d{3})-(\d{3})-(\d{4})"

strSearchString = "555-123-4567"
strNewString = objRegEx.Replace _
(strSearchString, "($1) $2-$3")

Wscript.Echo strNewString

En este caso lo que hacemos es buscar 3 dígitos (\d{3}) seguidos por un guión, seguido a su vez por 3 dígitos más y un guión, seguido de 4 dígitos. Es decir, buscamos lo siguiente, donde cada X representa un número del 0 al 9:

XXX-XXX-XXXX

Nota: así pues, ¿cómo sabemos que \d{3} indicará al script que busque tres números seguidos los unos de los otros? Bien, creemos haberlo leído en algún lugar. De hecho, tuvo que ser en el último e impactante capítulo final de El Código Da Vinci o en la Referencia del lenguaje de VBScript en MSDN® en línea (consulte go.microsoft.com/fwlink/?LinkID=111387).

Es estupendo poder buscar un número de teléfono arbitrario mediante las expresiones regulares. No obstante, todavía contamos con un problema. A fin de cuentas, no podemos reemplazar ese número de teléfono arbitrario con un número de teléfono también arbitrario. En su lugar, tenemos que usar el mismo número de teléfono, pero con un formato un tanto diferente. Pero, ¿cómo podemos conseguir esto?

Es sencillo, usando el siguiente texto de reemplazo:

"($1) $2-$3"

$1, $2 y $3 son ejemplos de una expresión regular "referencia inversa". Una referencia inversa es simplemente una parte del texto encontrado que se puede guardar y reutilizarse a continuación. En este script en particular, buscamos tres "subcoincidencias":

  • Un conjunto de 3 dígitos
  • Un conjunto de 3 dígitos más
  • Un conjunto de 4 dígitos

A cada una de estas tres subcoincidencias se le asigna automáticamente una referencia inversa: la primera subcoincidencia es $1, la segunda es $2 y así sucesivamente hasta $9. En otras palabras, en este script a las tres partes de nuestro número de teléfono se le asignan automáticamente las referencias inversas mostradas en la Figura 2.

Figure 2 Referencias inversas de números de teléfono

Parte del número de teléfono Referencia inversa
555 $1
123 $2
4567 $3

En nuestra cadena de reemplazo, usamos estas referencias inversas para garantizar que se reutiliza el número de teléfono correcto. Nuestro texto de reemplazo dice simplemente esto: adopte la primera referencia inversa ($1) y enciérrela entre paréntesis. Deje un espacio y, a continuación, inserte la segunda referencia inversa ($2) seguida de un guión. Por último, fije la tercera referencia inversa ($3).

¿Qué resultado nos ofrecen estas operaciones? Nos ofrecerán un número de teléfono que tendrá este aspecto:

(555) 123-4567

No está mal, no está nada mal.

Ésta es una variación del script del número de teléfono. Suponga que su empresa ha instalado un sistema de telefonía completamente nuevo y, como parte del cambio, todos los números de teléfono tendrán ahora el mismo prefijo. Si los números antes empezaban todos, por ejemplo, por 666, 777 u 888, ahora todos empiezan por 333. ¿Podemos volver a dar formato a los números de teléfono y cambiar el prefijo del número de teléfono, todo al mismo tiempo? Por supuesto que podemos:

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "(\d{3})-(\d{3})-(\d{4})"

strSearchString = "555-123-4567"
strNewString = objRegEx.Replace _
(strSearchString,"($1) 333-$3")

Wscript.Echo strNewString

¿Desea ver lo que hicimos aquí? Simplemente eliminamos el prefijo anterior (la referencia inversa $2) en nuestro texto de reemplazo. En su lugar, sustituimos el valor de prefijo codificado, y ahora estándar, 333. ¿Qué aspecto tendrá el número de teléfono 555-123-4567 tras ejecutar este script modificado? Debe tener un aspecto bastante parecido a este:

(555) 333-4567

Éste es otro de los usos más comunes de las referencias inversas: Suponga que tenemos un valor de cadena con un aspecto similar al siguiente:

Myer, Ken

¿Hay alguna manera de cambiar ese valor y mostrar el nombre de esta manera?

Ken Myer

Si no la hubiera, pareceríamos estúpidos, ¿verdad? Le presentamos un script que hace lo que hemos comentado:

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "(\S+), (\S+)"

strSearchString = "Myer, Ken"
strNewString = objRegEx.Replace _

strSearchString,"$2 $1")

Wscript.Echo strNewString

En este script en particular, buscamos una palabra, (\S+), seguida de una coma, seguida a su vez de un espacio en blanco, seguido de otra palabra. (En este caso, usamos \S+ para representar una "palabra"). La construcción \S+ significa cualquier conjunto consecutivo de caracteres distintos de espacios en blanco. En otras palabras, podríamos tener una letra, un número, un símbolo. De hecho, podríamos tener cualquier cosa que no fuera un espacio, un tabulador o un retorno de carro y un salto de línea. Tal como puede observar, esperamos encontrar dos subcoincidencias aquí: una que representa el apellido ($1) y otra que representa el nombre ($2). Gracias a esto, podemos mostrar el nombre de usuario como Nombre Apellido si usamos esta sintaxis:

"$2 $1"

¿Dónde está la coma? Bien, obviamente no la necesitamos, así que nos deshicimos de ella.

Nota: es gracioso, por alguna razón también pensamos en el editor de scripting. Ejem...

Antes de que se acabe el día, le vamos a mostrar una más. (Vale, de acuerdo, antes de que se acabe el mes). Esto no es 100% a prueba de duros de mollera; a fin de cuentas, no deseamos que un artículo introductorio como éste sea demasiado complicado. (Y las expresiones regulares tienen la capacidad de poder volverse bastante complicadas). No obstante, le mostramos un script que, en la mayoría de los casos, eliminará los primeros ceros de un valor como 0000.34500044:

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "\b0{1,}\."

strSearchString = _
"The final value was 0000.34500044."
strNewString = objRegEx.Replace _
strSearchString,".")

script.Echo strNewString

Como de costumbre, la única razón por la que esto funciona es gracias al patrón: "\b0{1,}\." Comenzamos buscando un límite de palabra (\b). De este modo nos aseguramos de no eliminar los ceros en un valor como 100.546. A continuación, buscamos uno o más ceros, 0{1,}, seguidos por un punto (\.). Si encontramos el patrón, reemplazamos estos ceros (y el punto) con un único punto ".". Si todo va según lo previsto, nuestra cadena quedará transformada en esto:

The final value was .34500044.

Y ya no tenemos más tiempo por este mes. Antes de despedirnos, debemos dejar claro que la Mona Lisa ha sido objeto de numerosas controversias antes incluso de que Leonardo terminara de pintarla. ¿Quién es esa misteriosa mujer? ¿Por qué sonríe de ese modo? ¿Por qué no tiene cejas? Varios historiadores especialistas en arte sugieren que Mona Lisa podría no ser una mujer, que la obra es, por contra, un autorretrato de Leonardo da Vinci. (Si eso es verdad, Leonardo debería cambiar de sastre). A su vez, la fundación educativa Unarius, ha ido un paso más allá y proclama que la obra es realmente el retrato del "alma gemela" de Leonardo en el "más allá" y que este alma gemela guiaba la mano de Leonardo. ¡Vaya coincidencia! Así es como se ha escrito la columna ¡Hola, encargados del scripting! de este mes.

Lo cual significa que debe dirigir todas sus quejas a el-alma-gemela-del-encargado-del-scripting-que-escribe-esta-columna@el-otro-microsoft.com. Gracias.

Dr. Scripto's Scripting Perplexer

El desafío mensual que prueba no sólo sus habilidades para resolver rompecabezas, también sus habilidades para el scripting.

Mayo 2008: Script-doku

Este mes, vamos a jugar al Sudoku, pero con un pequeño cambio. En vez de contar con los números 1 al 9 en la cuadrícula, tenemos las letras y símbolos que constituyen un cmdlet de Windows PowerShell™. En la solución final, al leer una de las filas de la cuadrícula obtendremos el nombre del cmdlet.

Nota: si aún no sabe cómo jugar al Sudoku, probablemente haya miles de sitios web en Internet donde podrá obtener las instrucciones, por lo que no vamos a explicarlo aquí, lo sentimos.

ANSWER:

Dr. Scripto's Scripting Perplexer

Respuesta: Script-doku, mayo 2008

Los encargados del scripting trabajan para Microsoft, mejor dicho, están contratados por Microsoft. Cuando no juegan al béisbol, ni entrenan ni lo ven (u otras actividades varias), dirigen el TechNet Script Center. Visite la página web www.scriptingguys.com.

© 2008 Microsoft Corporation and CMP Media, LLC. Reservados todos los derechos; queda prohibida la reproducción parcial o total sin previa autorización.