Compartir a través de


Todo lo que siempre quisiste saber sobre las tablas hash

Quiero dar un paso atrás y hablar de tablas hash. Los uso todo el tiempo. Estaba enseñando a alguien sobre ellos después de nuestra reunión de grupo de usuarios anoche y me di cuenta de que tenía la misma confusión sobre ellos que él. Las tablas hash son realmente importantes en PowerShell, por lo que es bueno tener una comprensión sólida de ellas.

Nota:

La versión original de este artículo apareció en el blog escrito por @KevinMarquette. El equipo de PowerShell agradece a Kevin por compartir este contenido con nosotros. Consulte su blog en PowerShellExplained.com.

Tabla hash como una colección de elementos

Quiero que veas primero una tabla Hash como una colección en la definición tradicional de una tabla hash. Esta definición le proporciona una comprensión fundamental de cómo funcionan cuando se usan para cosas más avanzadas más adelante. Omitir esta comprensión suele ser una fuente de confusión.

¿Qué es una matriz?

Antes de saltar a lo que es una tabla Hash , necesito mencionar primero matrices . Para este análisis, una matriz es una lista o colección de valores u objetos.

$array = @(1,2,3,5,7,11)

Una vez que tenga los elementos en una matriz, puede usar foreach para iterar en la lista o usar un índice para tener acceso a elementos individuales de la matriz.

foreach($item in $array)
{
    Write-Output $item
}

Write-Output $array[3]

También puede actualizar los valores mediante un índice de la misma manera.

$array[2] = 13

Acabo de tocar la superficie de las matrices, pero esto debería ponerlas en el contexto correcto cuando avance hacia las tablas hash.

¿Qué es una tabla hash?

Voy a empezar con una descripción técnica básica de lo que son las tablas hash, en el sentido general, antes de cambiar a las otras formas en que PowerShell las usa.

Una tabla hash es una estructura de datos, de forma muy similar a una matriz, salvo que se almacena cada valor (objeto) mediante una clave. Es un almacén básico de clave y valor. En primer lugar, creamos una tabla hash vacía.

$ageList = @{}

Nótese que las llaves, en lugar de los paréntesis, se utilizan para definir una tabla hash. A continuación, agregamos un elemento con una clave como esta:

$key = 'Kevin'
$value = 36
$ageList.Add( $key, $value )

$ageList.Add( 'Alex', 9 )

El nombre de la persona es la clave y su edad es el valor que quiero guardar.

Uso de los corchetes para el acceso

Una vez que agregue los valores a la tabla hash, puede extraerlos con esa misma clave (en lugar de usar un índice numérico como tendría para una matriz).

$ageList['Kevin']
$ageList['Alex']

Cuando quiero la edad de Kevin, uso su nombre para acceder a esta información. También podemos usar este enfoque para agregar o actualizar valores en la tabla hash. Esto es igual que usar el Add() método anterior.

$ageList = @{}

$key = 'Kevin'
$value = 36
$ageList[$key] = $value

$ageList['Alex'] = 9

Hay otra sintaxis que puede usar para acceder a los valores y actualizarlos que trataré en una sección posterior. Si vienes a PowerShell desde otro lenguaje, estos ejemplos deberían encajar con la forma en que posiblemente hayas usado hashtables antes.

Creación de tablas hash con valores

Hasta ahora he creado una tabla hash vacía para estos ejemplos. Puede rellenar previamente las claves y los valores al crearlas.

$ageList = @{
    Kevin = 36
    Alex  = 9
}

Como tabla de búsqueda

El valor real de este tipo de tabla hash es que puede usarlos como una tabla de búsqueda. Este es un ejemplo sencillo.

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

$server = $environments[$env]

En este ejemplo, especificará un entorno para la $env variable y seleccionará el servidor correcto. Puede usar un switch($env){...} para una selección como esta, pero una tabla hash es una buena opción.

Esto resulta aún mejor cuando se compila dinámicamente la tabla de búsqueda para usarla más adelante. Por lo tanto, piense en el uso de este enfoque cuando necesite realizar referencias cruzadas a algo. Creo que veríamos esto aún más si PowerShell no fuera tan bueno al filtrar en la canalización con Where-Object. Si alguna vez está en una situación en la que el rendimiento es importante, es necesario tener en cuenta este enfoque.

No diré que es más rápido, pero encaja en la regla de Si el rendimiento es importante, pruébelo.

Selección múltiple

Por lo general, se piensa en una tabla hash como un par clave-valor, donde se proporciona una clave y se obtiene un valor. PowerShell permite proporcionar una matriz de claves para obtener varios valores.

$environments[@('QA','DEV')]
$environments[('QA','DEV')]
$environments['QA','DEV']

En este ejemplo, uso la misma tabla hash de búsqueda anterior y proporciona tres estilos de matriz diferentes para obtener las coincidencias. Se trata de una gema oculta en PowerShell de la que la mayoría de las personas no son conscientes.

Iteración de tablas hash

Dado que una tabla hash es una colección de pares clave-valor, se recorre en iteración de forma diferente a la de una matriz o una lista normal de elementos.

Lo primero que hay que observar es que si canaliza la tabla hash, la canalización la trata como un objeto.

PS> $ageList | Measure-Object
count : 1

Aunque la Count propiedad indica cuántos valores contiene.

PS> $ageList.Count
2

Puede solucionar este problema mediante el uso de la Values propiedad si todo lo que necesita es solo los valores.

PS> $ageList.Values | Measure-Object -Average
Count   : 2
Average : 22.5

A menudo es más útil enumerar las claves y usarlas para acceder a los valores.

PS> $ageList.Keys | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_, $ageList[$_]
    Write-Output $message
}
Kevin is 36 years old
Alex is 9 years old

Este es el mismo ejemplo con un foreach(){...} bucle .

foreach($key in $ageList.Keys)
{
    $message = '{0} is {1} years old' -f $key, $ageList[$key]
    Write-Output $message
}

Vamos a recorrer cada clave en la tabla hash y, a continuación, usarla para acceder al valor. Este es un patrón común al trabajar con tablas hash como una colección.

GetEnumerator()

Eso nos lleva a GetEnumerator() para recorrer en iteración nuestra tabla hash.

$ageList.GetEnumerator() | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_.Key, $_.Value
    Write-Output $message
}

El enumerador proporciona cada par clave-valor uno después de otro. Se diseñó específicamente para este caso de uso. Gracias a Mark Kraus por recordarme este.

EnumeraciónIncorrecta

Un detalle importante es que no se puede modificar una tabla hash mientras se enumera. Si empezamos con nuestro ejemplo básico $environments :

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

Al intentar establecer cada clave en el mismo valor de servidor, se produce un error.

$environments.Keys | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

An error occurred while enumerating through a collection: Collection was modified;
enumeration operation may not execute.
+ CategoryInfo          : InvalidOperation: tableEnumerator:HashtableEnumerator) [],
 RuntimeException
+ FullyQualifiedErrorId : BadEnumeration

Esto también fallará aunque parezca que también debería funcionar correctamente:

foreach($key in $environments.Keys) {
    $environments[$key] = 'SrvDev03'
}

Collection was modified; enumeration operation may not execute.
    + CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException

El truco para esta situación es clonar las claves antes de realizar la enumeración.

$environments.Keys.Clone() | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

Nota:

No se puede clonar una tabla hash que contenga una sola clave. PowerShell produce un error. En su lugar, convierte la propiedad Keys en una matriz y, a continuación, recorre en iteración la matriz.

@($environments.Keys) | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

Tabla hash como una colección de propiedades

Hasta ahora, el tipo de objetos que colocamos en nuestra tabla hash era todo el mismo tipo de objeto. Usé edades en todos esos ejemplos y la clave era el nombre de la persona. Esta es una excelente manera de verlo cuando la colección de objetos tiene un nombre. Otra manera común de usar tablas hash en PowerShell es contener una colección de propiedades donde la clave es el nombre de la propiedad. Voy a profundizar en esa idea en este siguiente ejemplo.

Acceso basado en propiedades

El uso del acceso basado en propiedades cambia la dinámica de tablas hash y cómo puede usarlas en PowerShell. Este es nuestro ejemplo habitual anterior de tratar las claves como propiedades.

$ageList = @{}
$ageList.Kevin = 35
$ageList.Alex = 9

Al igual que en los ejemplos anteriores, este ejemplo agrega esas claves si aún no existen en la tabla hash. Dependiendo de cómo haya definido las claves y cuáles son sus valores, esto es un poco extraño o un ajuste perfecto. El ejemplo de lista de edades ha funcionado muy bien hasta este momento. Necesitamos un nuevo ejemplo para que esto se sienta correcto en el futuro.

$person = @{
    name = 'Kevin'
    age  = 36
}

Y podemos agregar y acceder a atributos en el $person de esta manera.

$person.city = 'Austin'
$person.state = 'TX'

De repente, esta tabla hash empieza a comportarse y sentirse como un objeto. Sigue siendo una colección de cosas, por lo que se siguen aplicando todos los ejemplos anteriores. Solo lo abordamos desde un punto de vista diferente.

Comprobación de claves y valores

En la mayoría de los casos, puede probar el valor con algo parecido a esto:

if( $person.age ){...}

Es simple pero ha sido la fuente de muchos errores para mí porque estaba pasando por alto un detalle importante en mi lógica. Empecé a usarlo para probar si una clave estaba presente. Cuando el valor era $false o cero, esa instrucción devolvería $false inesperadamente.

if( $person.age -ne $null ){...}

Esto soluciona ese problema con valores cero, pero no $null en comparación con claves no existentes. La mayoría de las veces no es necesario hacer esa distinción, pero hay métodos para cuando lo hace.

if( $person.ContainsKey('age') ){...}

También tenemos un ContainsValue() elemento para la situación en la que necesita probar un valor sin conocer la clave ni iterar toda la colección.

Eliminación y limpieza de claves

Puede eliminar las claves con el método Remove().

$person.Remove('age')

Al asignarles un $null valor, simplemente se queda con una clave que tiene un $null valor.

Una manera común de borrar una tabla hash consiste simplemente en inicializarla en una tabla hash vacía.

$person = @{}

Aunque eso funciona, intente usar el Clear() método en su lugar.

$person.Clear()

Se trata de una de esas instancias en las que el uso del método crea código autodocumentado y hace que las intenciones del código sean muy limpias.

Todas las cosas divertidas

Tablas hash ordenadas

De forma predeterminada, las tablas hash no están ordenadas (ni clasificadas). En el contexto tradicional, el orden no importa cuando siempre se usa una clave para acceder a los valores. Es posible que desee que las propiedades permanezcan en el orden en que las defina. Afortunadamente, hay una manera de hacerlo con la ordered palabra clave .

$person = [ordered]@{
    name = 'Kevin'
    age  = 36
}

Ahora, cuando se enumeran las claves y los valores, permanecen en ese orden.

Tablas hash en línea

Al definir una tabla hash en una línea, puede separar los pares clave-valor con un punto y coma.

$person = @{ name = 'kevin'; age = 36; }

Esto le será útil si los está creando en la tubería.

Expresiones personalizadas en comandos de canalización comunes

Hay algunos cmdlets que admiten el uso de tablas hash para crear propiedades personalizadas o calculadas. Normalmente, esto se ve con Select-Object y Format-Table. Las tablas hash tienen una sintaxis especial que tiene este aspecto cuando se expande por completo.

$property = @{
    Name = 'TotalSpaceGB'
    Expression = { ($_.Used + $_.Free) / 1GB }
}

Name es lo que el cmdlet etiquetaría esa columna. Expression es un bloque de script que se ejecuta donde $_ es el valor del objeto en la canalización. Este es el script en acción:

$drives = Get-PSDrive | where Used
$drives | Select-Object -Property Name, $property

Name     TotalSpaceGB
----     ------------
C    238.472652435303

Lo he colocado en una variable, pero podría definirse fácilmente directamente en el código y se puede acortar Name a n y Expression a e ya que está en eso.

$drives | Select-Object -Property Name, @{n='TotalSpaceGB';e={($_.Used + $_.Free) / 1GB}}

Personalmente no me gusta cómo alarga los comandos y a menudo promueve algunos malos comportamientos en los que no voy a profundizar. Es más probable que yo prefiera crear una tabla hash o pscustomobject con todos los campos y propiedades que quiero en vez de utilizar este enfoque en los scripts. Pero hay un montón de código por ahí que hace esto así que quería que lo sepas. Hablo de la creación de un pscustomobject elemento más adelante.

Expresión de ordenación personalizada

Es fácil ordenar una colección si los objetos tienen los datos en los que desea ordenar. Puede agregar los datos al objeto antes de ordenarlos o crear una expresión personalizada para Sort-Object.

Get-ADUser | Sort-Object -Property @{ e={ Get-TotalSales $_.Name } }

En este ejemplo, voy a tomar una lista de usuarios y usar algún cmdlet personalizado para obtener información adicional solo para la ordenación.

Ordenar una lista de tablas hash

Si tiene una lista de tablas hash que desea ordenar, verá que el Sort-Object no trata sus claves como propiedades. Podemos eludir eso utilizando una expresión de ordenación personalizada.

$data = @(
    @{name='a'}
    @{name='c'}
    @{name='e'}
    @{name='f'}
    @{name='d'}
    @{name='b'}
)

$data | Sort-Object -Property @{e={$_.name}}

Despliegue de tablas hash en cmdlets

Esta es una de mis cosas favoritas sobre hashtables que muchas personas no descubren al principio. La idea es que, en lugar de proporcionar todas las propiedades a un cmdlet en una línea, puede empaquetarlas primero en una tabla hash. A continuación, puede proporcionar la tabla hash a la función de forma especial. Este es un ejemplo de cómo crear un ámbito DHCP de la manera normal.

Add-DhcpServerV4Scope -Name 'TestNetwork' -StartRange '10.0.0.2' -EndRange '10.0.0.254' -SubnetMask '255.255.255.0' -Description 'Network for testlab A' -LeaseDuration (New-TimeSpan -Days 8) -Type "Both"

Sin usar splatting, todas esas cosas deben definirse en una sola línea. Se desplaza fuera de la pantalla o se ajustará donde le parezca. Ahora compare eso con un comando que utiliza 'splatting'.

$DHCPScope = @{
    Name          = 'TestNetwork'
    StartRange    = '10.0.0.2'
    EndRange      = '10.0.0.254'
    SubnetMask    = '255.255.255.0'
    Description   = 'Network for testlab A'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type          = "Both"
}
Add-DhcpServerV4Scope @DHCPScope

El uso del @ signo en lugar del $ es lo que invoca la operación de splat.

Dedique un momento a apreciar lo fácil que es leer ese ejemplo. Son el mismo comando exactamente con todos los mismos valores. La segunda es más fácil de entender y mantener en el futuro.

Utilizo splatting cada vez que el comando se vuelve excesivamente largo. Defino como demasiado largo cuando mi ventana necesite desplazarse hacia la derecha. Si encuentro tres propiedades de una función, es probable que la reescriba utilizando una tabla hash desplegada.

Descomposición para parámetros opcionales

Una de las formas más comunes en que utilizo la expansión para lidiar con parámetros opcionales que tienen origen en otras partes de mi script. Supongamos que tengo una función que encapsula una Get-CimInstance llamada que tiene un argumento opcional $Credential .

$CIMParams = @{
    ClassName = 'Win32_BIOS'
    ComputerName = $ComputerName
}

if($Credential)
{
    $CIMParams.Credential = $Credential
}

Get-CimInstance @CIMParams

Empiezo por crear mi tabla hash con parámetros comunes. Luego, agrego $Credential si existe. Dado que estoy utilizando "splatting" aquí, solo necesito realizar la llamada a Get-CimInstance en mi código una vez. Este patrón de diseño es muy limpio y puede controlar una gran cantidad de parámetros opcionales fácilmente.

Para ser justos, puede escribir los comandos para permitir $null valores para los parámetros. Simplemente no siempre tiene control sobre los otros comandos a los que llama.

Varios splats

Puede expandir varias tablas hash en el mismo cmdlet. Si volvemos a examinar nuestro ejemplo original de uso de splat:

$Common = @{
    SubnetMask  = '255.255.255.0'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type = "Both"
}

$DHCPScope = @{
    Name        = 'TestNetwork'
    StartRange  = '10.0.0.2'
    EndRange    = '10.0.0.254'
    Description = 'Network for testlab A'
}

Add-DhcpServerv4Scope @DHCPScope @Common

Usaré este método cuando tenga un conjunto común de parámetros que paso a una gran cantidad de comandos.

Splatting para código limpio

No hay nada malo con la expansión de un único parámetro si hace que el código sea más limpio.

$log = @{Path = '.\logfile.log'}
Add-Content "logging this command" @log

Expansión de ejecutables

Splatting también funciona en algunos ejecutables que usan la sintaxis /param:value. Robocopy.exe, por ejemplo, tiene algunos parámetros como este.

$robo = @{R=1;W=1;MT=8}
robocopy source destination @robo

No sé que esto es tan útil, pero lo encontré interesante.

Adición de tablas hash

Las tablas hash admiten el operador de suma para combinar dos tablas hash.

$person += @{Zip = '78701'}

Esto solo funciona si las dos tablas hash no comparten una clave.

Tablas hash anidadas

Podemos usar tablas hash como valores dentro de una tabla hash.

$person = @{
    name = 'Kevin'
    age  = 36
}
$person.location = @{}
$person.location.city = 'Austin'
$person.location.state = 'TX'

Comencé con una tabla hash básica que contiene dos claves. He agregado una clave llamada location con una tabla hash vacía. A continuación, agrego los dos últimos elementos a esa location tabla hash. También podemos hacer esto en línea.

$person = @{
    name = 'Kevin'
    age  = 36
    location = @{
        city  = 'Austin'
        state = 'TX'
    }
}

Esto crea la misma tabla hash que vimos anteriormente y puede acceder a las propiedades de la misma manera.

$person.location.city
Austin

Hay muchas maneras de abordar la estructura de los objetos. Esta es una segunda manera de examinar una tabla hash anidada.

$people = @{
    Kevin = @{
        age  = 36
        city = 'Austin'
    }
    Alex = @{
        age  = 9
        city = 'Austin'
    }
}

Esto combina el concepto de usar tablas hash tanto como colección de objetos como de propiedades. Los valores siguen siendo de fácil acceso incluso cuando se anidan con el enfoque que prefiera.

PS> $people.kevin.age
36
PS> $people.kevin['city']
Austin
PS> $people['Alex'].age
9
PS> $people['Alex']['City']
Austin

Tiendo a usar la propiedad dot cuando lo trato como una propiedad. Por lo general, esas son cosas que he definido estáticamente en mi código y que conozco de memoria. Si necesito recorrer la lista o acceder mediante programación a las claves, uso los corchetes para proporcionar el nombre de la clave.

foreach($name in $people.Keys)
{
    $person = $people[$name]
    '{0}, age {1}, is in {2}' -f $name, $person.age, $person.city
}

La capacidad de anidar tablas hash le brinda una gran flexibilidad y una variedad de opciones.

Examinar tablas hash anidadas

En cuanto empiece a anidar tablas hash, necesitará una manera fácil de verlas desde la consola. Si tomo esa última tabla hash, obtengo una salida que parece así y solo tiene esta profundidad.

PS> $people
Name                           Value
----                           -----
Kevin                          {age, city}
Alex                           {age, city}

Mi comando para ver estas cosas es ConvertTo-Json porque es muy limpio y uso json con frecuencia en otras cosas.

PS> $people | ConvertTo-Json
{
    "Kevin":  {
                "age":  36,
                "city":  "Austin"
            },
    "Alex":  {
                "age":  9,
                "city":  "Austin"
            }
}

Incluso si no conoce JSON, debería poder ver lo que está buscando. Hay un Format-Custom comando para datos estructurados como este, pero aún me gusta la vista JSON mejor.

Creación de objetos

A veces solo necesita tener un objeto y usar una tabla de hash para contener atributos no está funcionando correctamente. Más comúnmente, tú quieres ver las claves como nombres de columna. Un pscustomobject hace que sea fácil.

$person = [pscustomobject]@{
    name = 'Kevin'
    age  = 36
}

$person

name  age
----  ---
Kevin  36

Incluso si no lo crea comopscustomobject inicialmente, siempre puede transformarlo más adelante cuando sea necesario.

$person = @{
    name = 'Kevin'
    age  = 36
}

[pscustomobject]$person

name  age
----  ---
Kevin  36

Ya tengo un artículo detallado sobre pscustomobject que deberías leer después de este. Se basa en muchas de las cosas aprendidas aquí.

Lectura y escritura de tablas hash a un archivo

Guardar en CSV

Tener dificultades para conseguir que una tabla hash se guarde en un CSV es una de las dificultades a las que me refería anteriormente. Convierta su tabla hash a pscustomobject, y se guardará correctamente en CSV. Ayuda si comienza con un pscustomobject para que se conserve el orden de las columnas. Pero puede convertirlo en un pscustomobject en línea si es necesario.

$person | ForEach-Object{ [pscustomobject]$_ } | Export-Csv -Path $path

De nuevo, consulte mi artículo sobre el uso de un pscustomobject.

Guardar una tabla hash anidada en el archivo

Si necesito guardar una tabla hash anidada en un archivo y volver a leerla de nuevo, uso los cmdlets JSON para hacerlo.

$people | ConvertTo-Json | Set-Content -Path $path
$people = Get-Content -Path $path -Raw | ConvertFrom-Json

Hay dos puntos importantes sobre este método. En primer lugar, que el JSON se escribe en varias líneas, por lo que necesito usar la -Raw opción para volver a leerlo en una sola cadena. El segundo punto es que el objeto importado ya no es un [hashtable]. Ahora es un [pscustomobject] y esto puede causar problemas si no lo espera.

Presta atención a las tablas hash profundamente anidadas. Al convertirlo en JSON, es posible que no obtenga los resultados esperados.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json

{
  "a": {
    "b": {
      "c": "System.Collections.Hashtable"
    }
  }
}

Utilice el parámetro Depth para asegurarse de haber expandido cada tabla hash anidada.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json -Depth 3

{
  "a": {
    "b": {
      "c": {
        "d": "e"
      }
    }
  }
}

Si necesita que sea una [hashtable] durante la importación, entonces debe usar los comandos Export-CliXml y Import-CliXml.

Conversión de JSON en tabla hash

Si necesita convertir JSON en un [hashtable], hay una manera de hacerlo con JavaScriptSerializer en .NET.

[Reflection.Assembly]::LoadWithPartialName("System.Web.Script.Serialization")
$JSSerializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new()
$JSSerializer.Deserialize($json,'Hashtable')

A partir de PowerShell v6, la compatibilidad con JSON utiliza NewtonSoft JSON.NET y agrega soporte para tablas hash.

'{ "a": "b" }' | ConvertFrom-Json -AsHashtable

Name      Value
----      -----
a         b

PowerShell 6.2 agregó el parámetro Depth a ConvertFrom-Json. La profundidad predeterminada es 1024.

Lectura directamente desde un archivo

Si tiene un archivo que contiene una tabla hash mediante la sintaxis de PowerShell, hay una manera de importarlo directamente.

$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create( $content )
$scriptBlock.CheckRestrictedLanguage( $allowedCommands, $allowedVariables, $true )
$hashtable = ( & $scriptBlock )

Importa el contenido del archivo en y scriptblock, a continuación, comprueba que no tiene ningún otro comando de PowerShell en él antes de ejecutarlo.

En ese sentido, ¿sabías que un manifiesto de módulo (el archivo .psd1) es simplemente una tabla hash?

Las claves pueden ser cualquier objeto

La mayoría de las veces, las claves son solo cadenas. Por lo tanto, podemos poner comillas alrededor de cualquier cosa y convertirlo en una clave.

$person = @{
    'full name' = 'Kevin Marquette'
    '#' = 3978
}
$person['full name']

Puedes hacer algunas cosas curiosas que quizás no te hayas dado cuenta de que puedes hacer.

$person.'full name'

$key = 'full name'
$person.$key

Sólo porque puedes hacer algo, no significa que debas. Ese último solo parece un error que espera a ocurrir y sería fácilmente malinterpretado por cualquier persona que lea el código.

Técnicamente, la clave no tiene que ser una cadena de caracteres, pero resulta más fácil de considerar si solo utiliza cadenas de caracteres. Sin embargo, la indexación no funciona bien con las claves complejas.

$ht = @{ @(1,2,3) = "a" }
$ht

Name                           Value
----                           -----
{1, 2, 3}                      a

El acceso a un valor de la tabla hash por su clave no siempre funciona. Por ejemplo:

$key = $ht.Keys[0]
$ht.$($key)
a
$ht[$key]
a

Cuando la clave es una matriz, debe encapsular la $key variable en una subexpresión para que se pueda usar con la notación de acceso a miembros (.). O bien, puede usar la notación de índice de matriz ([]).

Uso en variables automáticas

$PSBoundParameters

$PSBoundParameters es una variable automática que solo existe dentro del contexto de una función. Contiene todos los parámetros con los que se llamó a la función. Esto no es exactamente un hashtable, pero es lo suficientemente cercano como para tratarlo como uno.

Esto incluye la eliminación de claves y la expansión de las claves en otras funciones. Si te encuentras escribiendo funciones proxy, echa un vistazo más detallado a esta en particular.

Consulte about_Automatic_Variables para obtener más detalles.

Advertencia sobre PSBoundParameters

Lo importante que hay que recordar es que esto solo incluye los valores que se pasan como parámetros. Si también tiene parámetros con valores predeterminados, pero el llamador no los pasa, $PSBoundParameters no incluye esos valores. Esto suele pasarse por alto.

$PSDefaultParameterValues

Esta variable automática permite asignar valores predeterminados a cualquier cmdlet sin cambiar el cmdlet. Eche un vistazo a este ejemplo.

$PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"

Esto agrega una entrada a la $PSDefaultParameterValues tabla hash que establece UTF8 como valor predeterminado para el Out-File -Encoding parámetro . Esto es específico de la sesión, así que colóquelo en $PROFILE.

Lo uso a menudo para asignar previamente valores que escriba con bastante frecuencia.

$PSDefaultParameterValues[ "Connect-VIServer:Server" ] = 'VCENTER01.contoso.local'

Esto también acepta caracteres comodín, lo que le permite establecer valores en bloque. Estas son algunas maneras de usar esto:

$PSDefaultParameterValues[ "Get-*:Verbose" ] = $true
$PSDefaultParameterValues[ "*:Credential" ] = Get-Credential

Para obtener un desglose más detallado, consulte este excelente artículo sobre valores predeterminados automáticos de Michael Sorens.

Regex $Matches

Cuando se usa el -match operador , se crea una variable automática denominada $Matches con los resultados de la coincidencia. Si tiene alguna subexpresión en la expresión regular, también se muestran esas coincidencias secundarias.

$message = 'My SSN is 123-45-6789.'

$message -match 'My SSN is (.+)\.'
$Matches[0]
$Matches[1]

Coincidencias con nombre

Esta es una de mis características favoritas que la mayoría de las personas no conocen. Si usa una coincidencia de regex con nombre, puede acceder a esa coincidencia por nombre en las coincidencias.

$message = 'My Name is Kevin and my SSN is 123-45-6789.'

if($message -match 'My Name is (?<Name>.+) and my SSN is (?<SSN>.+)\.')
{
    $Matches.Name
    $Matches.SSN
}

En el ejemplo anterior, (?<Name>.*) es una subexpresión con nombre. A continuación, este valor se coloca en la $Matches.Name propiedad .

Group-Object -AsHashtable

Una característica poco conocida de Group-Object es que puede convertir algunos datasets en una tabla hash.

Import-Csv $Path | Group-Object -AsHashtable -Property Email

Esto agregará cada fila a una tabla hash y usará la propiedad especificada como clave para acceder a ella.

Copiando tablas hash

Una cosa importante que hay que saber es que las tablas hash son objetos. Y cada variable es simplemente una referencia a un objeto . Esto significa que se necesita más trabajo para realizar una copia válida de una tabla hash.

Asignación de tipos de referencia

Cuando tiene una tabla hash y la asigna a una segunda variable, ambas variables apuntan a la misma tabla hash.

PS> $orig = @{name='orig'}
PS> $copy = $orig
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [copy]

Esto resalta que son iguales porque modificar los valores de uno también modificará los valores de la otra. Esto también se aplica al pasar tablas de hash a otras funciones. Si esas funciones realizan cambios en esa tabla hash, también se modifica tu original.

Copias superficiales, nivel sencillo

Si tenemos una tabla hash simple como el ejemplo anterior, podemos usar Clone() para realizar una copia superficial.

PS> $orig = @{name='orig'}
PS> $copy = $orig.Clone()
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [orig]

Esto nos permitirá realizar algunos cambios básicos en uno que no afecte al otro.

Copias superficiales, anidadas

La razón por la que se denomina copia superficial es porque solo copia las propiedades de nivel base. Si una de esas propiedades es un tipo de referencia (como otra tabla hash), esos objetos anidados seguirán apuntando entre sí.

PS> $orig = @{
        person=@{
            name='orig'
        }
    }
PS> $copy = $orig.Clone()
PS> $copy.person.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.person.name
PS> 'Orig: [{0}]' -f $orig.person.name

Copy: [copy]
Orig: [copy]

Por lo tanto, puede ver que aunque he clonado la tabla hash, la referencia a person no se ha clonado. Es necesario hacer una copia profunda para tener realmente una segunda tabla hash que no esté vinculada a la primera.

Copias en profundidad

Hay un par de maneras de realizar una copia profunda de una tabla de dispersión (conservándola como una tabla de dispersión). Esta es una función mediante PowerShell para crear de forma recursiva una copia en profundidad:

function Get-DeepClone
{
    [CmdletBinding()]
    param(
        $InputObject
    )
    process
    {
        if($InputObject -is [hashtable]) {
            $clone = @{}
            foreach($key in $InputObject.Keys)
            {
                $clone[$key] = Get-DeepClone $InputObject[$key]
            }
            return $clone
        } else {
            return $InputObject
        }
    }
}

No controla ningún otro tipo de referencia o matrices, pero es un buen punto de partida.

Otra manera es usar .NET para deserializarlo mediante CliXml como en esta función:

function Get-DeepClone
{
    param(
        $InputObject
    )
    $TempCliXmlString = [System.Management.Automation.PSSerializer]::Serialize($obj, [int32]::MaxValue)
    return [System.Management.Automation.PSSerializer]::Deserialize($TempCliXmlString)
}

Para tablas hash extremadamente grandes, la función de deserialización es más rápida a medida que se expande. Sin embargo, hay algunos aspectos que se deben tener en cuenta al usar este método. Dado que usa CliXml, se consume mucha memoria y, si va a clonar tablas hash enormes, podría ser un problema. Otra limitación de CliXml es que hay una limitación de profundidad de 48. Es decir, si tiene una tabla hash con 48 capas de tablas hash anidadas, se producirá un error en la clonación y no se generará ninguna tabla hash.

¿Algo más?

He avanzado mucho rápidamente. Espero que te vayas habiendo aprendido algo nuevo o entendiéndolo mejor cada vez que lo leas. Dado que he tratado el espectro completo de esta característica, hay aspectos que pueden no aplicarse a usted en este momento. Eso es perfectamente correcto y es algo que se espera dependiendo de cuánto trabajes con PowerShell.