Windows PowerShellEscritura de expresiones regulares

Don Jones

192.168.4.5. \\Server57\Share. johnd@contoso.com. Seguramente reconoce estos tres elementos: una dirección IP, una ruta de acceso UNC (convención de nomenclatura universal) y una dirección de correo electrónico. Su cerebro reconoce su formato. Cuatro grupos de dígitos, barras diagonales inversas, el símbolo @ y otras indicaciones revelan los tipos de datos que estas cadenas de

caracteres representan. Con sólo pensar un poco, puede reconocer rápidamente que 192.168 por sí solo no constituye una dirección IP válida, que 7\\Server2\\Share no es una ruta de acceso UNC válida y que joe@contoso tampoco representa una dirección válida de correo electrónico.

Los equipos informáticos, desgraciadamente, tienen que esforzarse un poco más para "entender" formatos complicados como estos. Aquí es donde entran en juego las expresiones regulares. Una expresión regular es una cadena, escrita mediante un lenguaje especial de expresiones regulares, que ayuda a los equipos informáticos a identificar cadenas de un formato particular, por ejemplo, direcciones IP, rutas de acceso UNC y direcciones de correo electrónico. Una expresión regular bien escrita tiene la capacidad de permitir a un script de Windows PowerShellTM aceptar datos válidos y rechazar datos no válidos que no presentan el formato especificado por el usuario.

Establecimiento de coincidencias simples

El operador –match de Windows PowerShell compara cada cadena con una expresión regular, o regex, y devuelve el resultado Verdadero o Falso dependiendo de si la cadena coincide con el regex. Un regex muy sencillo ni siquiera necesita contener sintaxis especial; será suficiente el uso de caracteres literales. Por ejemplo:

"Microsoft" –match "soft"
"Software" –match "soft"
"Computers" –match "soft"

Cuando se ejecutan en Windows PowerShell, las dos primeras expresiones devuelven Verdadero, mientras que la tercera devuelve Falso. En cada una de ellas, la cadena viene seguida por el operador –match que, a su vez, precede a un regex. De forma predeterminada, el regex flotará por la cadena para encontrar una coincidencia. Los caracteres "soft" se encuentran tanto en "Software" como en "Microsoft", pero en posiciones diferentes. Además, fíjese que, de forma predeterminada, un regex no distingue mayúsculas de minúsculas: "soft" se encuentra en "Software" a pesar de la "S" mayúscula.

Sin embargo, si es necesario, un operador diferente, –cmatch, ofrece comparación de regex que distingue mayúsculas de minúsculas, por ejemplo:

"Software" –cmatch "soft"

Esta expresión devuelve Falso, ya que la cadena "soft" no coincide con "Software" en una comparación que distingue mayúsculas de minúsculas. Tenga en cuenta que el operador –imatch también está disponible como opción explícita que no distingue mayúsculas de minúsculas, a pesar de que este es el comportamiento predeterminado de –match.

Caracteres comodín y repetidores

Un regex puede contener algunos caracteres comodín. Un punto, por ejemplo, equivale a una instancia de cualquier carácter. Un signo de interrogación equivale a cero instancias o a una instancia de cualquier carácter. A continuación, se incluyen algunos ejemplos a modo de ilustración:

"Don" –match "D.n" (True)
"Dn" –match "D.n" (False)
"Don" –match "D?n" (True)
"Dn" –match "D?n" (True)

En la primera expresión, el punto equivale exactamente a un carácter, así que el resultado de la coincidencia es Verdadero. En la segunda expresión, el punto no encuentra el carácter que requiere para la inclusión, de modo que el resultado de la coincidencia es Falso. El signo de interrogación, como se muestra en las expresiones tercera y cuarta, puede coincidir con un solo carácter desconocido o con ningún carácter en absoluto. Finalmente, en el cuarto ejemplo, el resultado de la coincidencia es Verdadero porque "D" y "n" se presentan sin caracteres intermedios. Así, podemos considerar que el signo de interrogación representa un carácter opcional, y el resultado de la coincidencia continúa siendo Verdadero aunque no haya ningún carácter en esa posición.

Un regex también reconoce los símbolos * y + como repetidores. Estos símbolos deben seguir a algún carácter. El símbolo * equivale a cero o más instancias de los caracteres especificados, mientras que el símbolo + coincide con uno o más de los caracteres especificados. A continuación se muestran algunos ejemplos:

"DoDon" –match "Do*n" (True)
"Dn" -match "Do*n" (True)
"DoDon" -match "Do+n" (True)
"Dn" -match "Do+n" (False)

Vea que tanto * como + coinciden con "Do", no sólo con la "o". Esto se debe a que los repetidores están diseñados para coincidir con una serie de caracteres, no solamente con uno.

¿Qué sucede si necesita establecer coincidencias con el punto o los símbolos "*", "?" o "+"? Basta con usar una barra diagonal inversa precedente, que es el carácter de escape de regex:

"D.n" -match "D\.n" (True)

Fíjese que este carácter es diferente del carácter de escape de Windows PowerShell (el apóstrofo hacia atrás), pero sigue la sintaxis de regex estándar del sector.

Clases de caracteres

Una clase de caracteres es una forma más amplia de carácter comodín que representa un grupo entero de caracteres. Windows PowerShell reconoce numerosas clases de caracteres. Por ejemplo:

  • \w equivale a cualquier carácter de palabra, tanto letras como números.
  • \s equivale a cualquier carácter de espacio en blanco: tabulaciones, espacios, etc.
  • \d equivale a cualquier carácter de dígito.

También hay clases de caracteres negativos: \W equivale a caracteres que no son de palabra, \S equivale a caracteres que no son de espacio en blanco y \D equivale a caracteres que no son de dígito. Estas clases pueden aparecer seguidas por * o + para indicar que se aceptan varias coincidencias. A continuación se muestran algunos ejemplos:

"Shell" -match "\w" (True)
"Shell" -match "\w*" (True)

Cmdlet del mes

El cmdlet Write-Debug es muy útil para escribir objetos (por ejemplo, cadenas de texto) en la canalización de depuración. No obstante, el resultado de este cmdlet en el shell puede ser decepcionante, ya que da la impresión de que su efecto es nulo.

El truco es que la canalización de depuración está desactivada de forma predeterminada; la variable $DebugPreference está establecida en "SilentlyContinue". Por el contrario, si la establece en "Continue", todo lo que envíe con Write-Debug aparecerá en la consola como texto amarillo. Esta es una manera perfecta de agregar código de seguimiento a los scripts para comprobar la ejecución de scripts complejos. El color amarillo ayuda a distinguir entre el resultado del código de seguimiento y el resultado normal del script, y podrá anular los mensajes de depuración en cualquier momento sin tener que quitar todas las instrucciones Write-Debug. Simplemente vuelva a establecer $DebugPreference en "SilentlyContinue" y el texto de depuración quedará suprimido.

Aunque ambas expresiones devuelven Verdadero, las coincidencias que ofrecen son apreciablemente diferentes. Afortunadamente, hay un modo de averiguar las intenciones del operador –match: cada vez que se realiza una coincidencia, una variable especial llamada $matches se rellena con los resultados de la misma, es decir, con los caracteres de la cadena que el operador ha encontrado como coincidencia de su regex. La variable $matches retiene sus resultados hasta que se realiza otra coincidencia positiva con el operador –match. La figura 1 muestra la diferencia entre las dos expresiones que le acabo de mostrar. Como puede ver, \w coincide con la "S" de "Shell", mientras que el repetidor \w* coincide con la palabra entera.

Figura 1 Un * puede marcar una diferencia tremenda.

Figura 1** Un * puede marcar una diferencia tremenda. **(Hacer clic en la imagen para ampliarla)

Grupos, intervalos y tamaños de caracteres

Un regex también puede contener grupos o intervalos de caracteres encerrados en corchetes. Por ejemplo, [aeiou] significa que cualquiera de los caracteres incluidos ("a", "e", "i", "o" o "u") constituye una coincidencia aceptable. [a-zA-Z] indica que cualquier letra en el intervalo a-z o A-Z es aceptable (aunque si usa el operador –match que no distingue mayúsculas de minúsculas, será suficiente usar sólo a-z o sólo A-Z). Por ejemplo:

"Jeff" -match "J[aeiou]ff" (True)
"Jeeeeeeeeeeff" -match "J[aeiou]ff" (False)

También puede especificar un número mínimo y un número máximo de caracteres mediante llaves. {3} indica que quiere exactamente tres instancias del carácter especificado, {3,} significa que quiere por lo menos tres o más instancias, y {3,4} indica que quiere por lo menos tres pero no más de cuatro instancias. Esta es una forma ideal de crear regex para direcciones IP:

"192.168.15.20" -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" (True)

Este regex solicita cuatro grupos de dígitos de uno a tres dígitos cada uno, todos ellos separados por un punto literal. Pero considere este ejemplo:

"300.168.15.20" -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" (True)

Esto muestra las limitaciones de un regex. Aunque el formato de esta cadena parece una dirección IP, obviamente no es una dirección IP válida. Un regex no puede determinar si los datos son realmente válidos; sólo puede determinar si su aspecto es correcto.

Detenga la flotación

La solución de problemas relacionados con regex puede ser complicada. Por ejemplo, aquí tenemos un regex que comprueba una ruta de acceso UNC con el formato \\Server2\Share:

"\\Server2\Share" -match "\\\\\w+\\\w+" (True)

Aquí, el regex mismo es difícil de leer porque es necesario escapar cada barra diagonal inversa literal que quiero comprobar con una segunda barra diagonal inversa. Aunque esto parece funcionar bien, realmente no es así:

"57\\Server2\Share" -match "\\\\\w+\\\w+" (True)

Está claro (por lo menos para nosotros) que este segundo ejemplo no es una ruta de acceso UNC válida; sin embargo, el regex la aceptó. ¿Por qué? Recuerde que un regex flota de forma predeterminada. Este regex simplemente busca dos barras diagonales inversas, una o más instancias de letras y números, otra barra diagonal inversa, y más letras y números. Este patrón existe en la cadena, junto con otros dígitos al principio, que resultan en una ruta de acceso UNC no válida. Lo que hace falta es pedir al regex que empiece a realizar coincidencias al principio de la cadena, sin flotar. Esto se consigue así:

"57\\Server2\Share" -match "^\\\\\w+\\\w+" (False)

El carácter ^ indica que esta es la ubicación donde empieza la cadena. Con esta adición, la ruta de acceso UNC no válida da lugar a errores, ya que el regex espera que los dos primeros caracteres sean barras diagonales inversas y, en este caso, no lo son.

De forma similar, el símbolo $ se puede usar para indicar el final de una cadena. Esto no sería muy útil en el caso de una ruta de acceso UNC, porque este tipo de ruta puede contener segmentos de ruta adicionales, como \\Server2\Share\Folder\File. No obstante, estoy seguro de que hay muchas situaciones en las que desearía especificar el final de una cadena.

Ayuda con expresiones regulares

En Windows PowerShell, el tema de ayuda about_regular_expressions ofrece asistencia básica sobre sintaxis del lenguaje de regex, pero los recursos en línea pueden ofrecer aún más información. Por ejemplo, uno de mis sitios web favoritos, www.RegExLib.com, incluye una biblioteca gratuita de expresiones regulares que se han escrito con varios propósitos y que el público ha contribuido. Puede buscar palabras clave, como "correo electrónico" o "UNC", para localizar rápidamente un regex que cubra sus necesidades o que, al menos, ofrezca un buen punto de partida. Si logra crear un regex estupendo, puede remitirlo a la biblioteca para que otros usuarios puedan usarlo.

También me gusta RegexBuddy (www.RegexBuddy.com). Es una herramienta económica que proporciona un editor gráfico de regex. RegexBuddy simplifica el ensamblaje de regex complejos, además de facilitar la comprobación de cada regex para asegurarse de que acepta cadenas válidas y rechaza cadenas no válidas. Algunos desarrolladores de software han creado también editores y herramientas de comprobación de regex comerciales y shareware gratuito que los usuarios encontrarán de gran utilidad.

Uso de expresiones regulares

Quizás se pregunte para qué usaría un regex en la vida real. Imagine que está leyendo información de un archivo CSV y la usa para crear usuarios nuevos en Active Directory®. Si el archivo de CSV viene generado por otra persona, querrá comprobar que los datos que contiene presentan un aspecto apropiado. Un regex es perfecto para esta tarea. Un regex sencillo como \w+, por ejemplo, puede confirmar que los nombres y los apellidos no contienen caracteres especiales ni símbolos, mientras que algo más complicado puede confirmar que las direcciones de correo electrónico cumplen su estándar corporativo. Por ejemplo, podría usar esto:

"^[a-z]+\.[a-z]+@contoso.com$"

Este regex requiere una dirección de correo electrónico con el formato don.jones@contoso.com. El nombre y el apellido sólo pueden contener letras y deben estar separados por un punto. Por cierto, las direcciones de correo electrónico son las cadenas más complicadas a la hora de escribir regex. Si puede restringir el ámbito a un estándar corporativo particular, lo tendrá mucho más fácil.

No se olvide de los delimitadores de inicio y fin (^ y $), que garantizan que no hay caracteres ni símbolos detrás de contoso.com y que no hay ningún elemento precedente a los caracteres que forman el nombre del usuario.

Realmente, el uso de este regex en Windows PowerShell es bastante sencillo. Si damos por sentado que la variable $email contiene la dirección de correo electrónico que lee del archivo CSV, algo como lo siguiente servirá para comprobar su validez:

$regex = "^[a-z]+\.[a-z]+@contoso.com$"
If ($email –notmatch $regex) {
  Write-Error "Invalid e-mail address $email" 
}

Y, en este ejemplo, ha aprendido un operador nuevo. -notmatch devuelve Verdadero si la cadena no coincide con el regex proporcionado. También hay un –cnotmatch para comparaciones que distinguen mayúsculas de minúsculas.

Hay muchos más aspectos de las expresiones regulares que no he tratado aquí, entre ellos, otras clases de caracteres, operaciones más avanzadas y algún que otro operador. También está el tipo de objeto [regex] compatible con Windows PowerShell. Sin embargo, lo que he cubierto en esta introducción rápida sobre la sintaxis de regex será suficiente para que pueda ponerse manos a la obra. Visíteme siempre que lo desee en www.ScriptingAnswers.com si necesita ayuda con algún regex especialmente complicado.

Don Jones es redactor colaborador de la revista TechNet Magazine y coautor de Windows PowerShell: TFM (SAPIEN Press). Se dedica a enseñar Windows PowerShell (www.ScriptingTraining.com) y puede ponerse en contacto con él a través del sitio web ScriptingAnswers.com.

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