Capturar nombres con PowerShell y expresiones regulares
A medida que continúe aprendiendo y adoptando PowerShell, eventualmente encontrará expresiones regulares. Esperemos que muchos de vosotros ya tengáis algunos conocimientos fundamentales. si no, el primer lugar para comenzar es leyendo el tema de ayuda, about_regular_expressions. En este artículo, voy a presentarles un tema avanzado sobre expresiones regulares: llamado capturas. Admito que cuando supe este tema por primera vez me dio vueltas la cabeza. Pero espero poder frenar el tiovivo.
Comencemos con una cadena como esta.
$t = "2019-06-21 17:12:31Z : 172.16.1.123 [ Begin process data ]"
Esto es algo que podría tener en un archivo de registro. .Dado que la fortaleza de PowerShell proviene de su capacidad para trabajar con objetos, podría ser más fácil transformar el archivo de registro en una colección de objetos.
Comience con patrones
Para que esto funcione y pueda utilizar expresiones regulares, debe saber cómo se verán sus datos y deben ser consistentes y predecibles. En este ejemplo, tengo una cadena de fecha y hora, una dirección IPv4 y luego un mensaje entre corchetes. El primer paso es crear un patrón de expresión regular que coincida con estos diferentes elementos. Comencemos con la fecha y hora.
$t -match "\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}Z"
El patrón dice, encuentra algo que comience exactamente con 4 dígitos. \d significa un dígito y {4} significa exactamente 4 dígitos. Luego hay un guión seguido de exactamente 2 dígitos (\d{2}), otro guión y 2 dígitos. Eso debería darme la fecha. Pero hay un poco más. Necesito capturar el espacio (\s) y luego tengo un patrón que coincide con el tiempo que ya deberías reconocer. El final es una Z literal.
Como puede ver, esto coincide exactamente con lo que quiero. Por cierto, a menudo hay más de un patrón que puedes escribir. Intento ceñirme a patrones simples y claros. ¡Las expresiones regulares pueden ser bastante crípticas!
A continuación, necesito hacer coincidir la dirección IP.
$t -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"
El patrón dice comenzar con al menos 1 dígito y no más de 3. (\d{1,3}) seguido de un punto literal. (\.). El punto es un carácter de expresión regular especial que significa cualquier cosa.\es el carácter de escape que le indica a PowerShell que busque un punto literal. Este patrón se repite para los octetos restantes.
Este patrón no valida la dirección, solo que parece una dirección IP. Hay patrones que son más restrictivos pero no quería añadir más complejidad. Y en mi situación, sé que la dirección será válida.
El último es el texto entre corchetes.
$t -match "\[.*\]"
El corchete es un carácter especial de expresión regular, por lo que necesito escapar de él. Luego solicito hacer coincidir varias instancias (*) de cualquier carácter (.).
Definir los nombres
Una vez que tenga patrones coincidentes, puede definirlos con un nombre. El diseño general se ve así:
(?<capture-name>Your-Pattern)
Los paréntesis son clave. Mi patrón de fecha y hora se puede definir como una captura con nombre:
(?<date>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}Z)
Aquí hay un patrón que describe toda la línea de texto de principio a fin.
[regex]$rx = '(?<date>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}Z)\s:\s(?<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s\[\s(?<status>.*)\]'
Modifiqué el último patrón para no incluir los corchetes. Hay otras técnicas avanzadas de expresión regular que podría haber usado, pero las mantuve simples. Lo importante a tener en cuenta es que estoy definiendo $rx como un tipo especial de objeto. Este objeto regex me permite hacer mucho más que simplemente usar el operador -match. En esta situación, puedo usarlo para buscar coincidencias.
Esto es bueno. Pero ¿cómo entran en juego los nombres?
Uso de capturas con nombre en PowerShell
En el partido, puedes ver una propiedad de Grupos. Aquí es donde se pueden encontrar las capturas nombradas.
Con PowerShell es fácil obtener solo las capturas nombradas omitiendo el primer grupo coincidente.
¡Eso casi parece un objeto! no se necesita mucho más para crear un objeto real.
$m.groups | Select-Object -Skip 1 | foreach-object -begin { $h = @{} } -process {
$h.Add($_.name,$_.value.trim())
} -end {
[pscustomobject]$h
}
Aunque sería mejor si la fecha fuera un objeto de fecha y hora en lugar de una cadena. Aquí hay una alternativa, suponiendo que conozca el orden de las capturas con nombre.
$o = [pscustomobject]@{
Date = $m.groups[1].value -as [datetime]
IPAddress = $m.groups[2].value -as [ipaddress]
Status = $m.groups[3].value
}
Como puedes ver, este es un objeto.
Con este código, puedo escribir un script para procesar cada línea del archivo de registro y crear un objeto personalizado. Eso hace que sea mucho más fácil filtrar, ordenar o hacer cualquier otra cosa que necesite hacer con los datos.
Convertir desde texto
¿O qué tal una manera más fácil? Ahora que sabe cómo crear un patrón de expresión regular usando capturas con nombre, puede usar el comando ConvertFrom-Text en mi módulo PSScriptTools, que puede instalar desde la Galería de PowerShell. Con este comando puede crear código para convertir cualquier salida de texto en objetos de PowerShell.
$c = "(?<Protocol>\w{3})\s+(?<LocalIP>(\d{1,3}\.){3}\d{1,3}):(?<LocalPort>\d+)\s+(?<ForeignIP>.*):(?<ForeignPort>\d+)\s+(?<State>\w+)?"
netstat -an | select -skip 4 | convertfrom-text $c | where-object {$_.LocalIP -ne '0.0.0.0'} | format-table -autosize
Una vez que tenga al menos algunas habilidades básicas de expresión regular, se encontrará usándolas con frecuencia. Y como ocurre con cualquier idioma nuevo, cuanto más lo uses, más fluidez adquirirás.