tutorial - Seleccione los valores de una propiedad en todos los objetos de una matriz en PowerShell
powershell tutorial (3)
Como una solución aún más fácil, puedes usar:
$results = $objects.Name
Que debería llenar $results
con una matriz de todos los valores de propiedad ''Nombre'' de los elementos en $objects
.
Digamos que tenemos una matriz de objetos $ objetos. Digamos que estos objetos tienen una propiedad de "Nombre".
Esto es lo que quiero hacer
$results = @()
$objects | %{ $results += $_.Name }
Esto funciona, pero ¿se puede hacer de una mejor manera?
Si hago algo como:
$results = objects | select Name
$results
es una matriz de objetos que tienen una propiedad Name. Quiero $ resultados para contener una matriz de nombres.
¿Hay una mejor manera?
Creo que es posible que pueda usar el parámetro ExpandProperty
de Select-Object
.
Por ejemplo, para obtener la lista del directorio actual y simplemente mostrar la propiedad Nombre, uno haría lo siguiente:
ls | select -Property Name
Esto sigue devolviendo objetos DirectoryInfo o FileInfo. Siempre puede inspeccionar el tipo que llega a través de la tubería por tuberías a Get-Member (alias gm
).
ls | select -Property Name | gm
Entonces, para expandir el objeto para que sea del tipo de propiedad que está mirando, puede hacer lo siguiente:
ls | select -ExpandProperty Name
En su caso, puede hacer lo siguiente para que una variable sea una matriz de cadenas, donde las cadenas son la propiedad Nombre:
$objects = ls | select -ExpandProperty Name
Para complementar las respuestas preexistentes y útiles con orientación de cuándo usar qué enfoque y una comparación de rendimiento .
Fuera de una tubería, use:
$objects.Name (PSv3 +), como se demostró en la respuesta de rageandqq , que es sintácticamente más simple y mucho más rápido .
- Acceder a una propiedad en el nivel de colección para obtener los valores de sus miembros como una matriz se denomina enumeración de miembros y es una función de PSv3 + ;
- Compensaciones :
- Tanto la matriz de entrada como la de salida deben caber en la memoria como un todo .
- Si la colección de entrada es en sí misma el resultado de un comando (canalización) (por ejemplo,
(Get-ChildItem).Name
), ese comando primero debe ejecutarse hasta su finalización antes de que se pueda acceder a los elementos de la matriz resultante.
En una tubería donde el resultado debe procesarse más a fondo o los resultados no encajan en la memoria como un todo, use:
$objects | Select-Object -ExpandProperty Name
- La necesidad de
-ExpandProperty
se explica en la respuesta de Scott Saad . - Obtendrá los beneficios normales de la línea de procesamiento uno por uno, que normalmente produce la salida de inmediato y mantiene el uso de la memoria constante (a menos que finalmente recopile los resultados en la memoria de todos modos).
- Tradeoff :
- El uso de la tubería es comparativamente lento .
- La necesidad de
Para pequeñas colecciones de entrada (matrices), probablemente no notará la diferencia , y, especialmente en la línea de comandos, a veces es más importante poder escribir el comando fácilmente.
Aquí hay una alternativa fácil de escribir , que, sin embargo, es el enfoque más lento ; usa una sintaxis ForEach-Object
simplificada llamada una instrucción de operación (nuevamente, PSv3 +):; por ejemplo, la siguiente solución PSv3 + es fácil de agregar a un comando existente:
$objects | % Name # short for: $objects | ForEach-Object -Process { $_.Name }
En aras de la exhaustividad: el operador de recopilación de PSv4 + .ForEach()
poco conocido es otra alternativa :
# By property name (string):
$objects.ForEach(''Name'')
# By script block (much slower):
$objects.ForEach({ $_.Name })
Este enfoque es similar a la enumeración de miembros (los mismos intercambios), excepto más lento (aunque aún notablemente más rápido que la interconexión).
Para extraer un único valor de propiedad por nombre (argumento de cadena ), esta solución está a la par con la enumeración de miembros (aunque la última es sintácticamente más simple).
La variante script-block , aunque mucho más lenta, permite transformaciones arbitrarias; es una alternativa más rápida, todo en memoria a la vez, al cmdlet
ForEach-Object
basado enForEach-Object
.
Comparando el desempeño de los diferentes enfoques
Aquí están los tiempos de muestreo para los diversos enfoques, basados en una colección de entrada de 100,000
objetos ; los números absolutos no son importantes y varían en función de muchos factores, pero deberían proporcionarle una sensación de rendimiento relativo :
Approach Seconds
-------- -------
$objects.ForEach(''Number'') 0.060
$objects.Number 0.065
$objects | Select-Object -ExpandProperty Number 0.859
$objects.ForEach({ $_.Number }) 1.008
$objects | % Number 4.380
La solución más rápida basada en canalización es más de 10 veces más lenta que las soluciones de operador de recopilación basadas en enumeración / nombre de propiedad.
El uso de un bloque de scripts en cada iteración ralentiza las cosas dramáticamente, de modo que la solución más rápida basada en canalización (
Select-Object -Expand
) supera a la collection-operativa basada en bloques de scripts% Number
es lo peor de ambos mundos: una canalización que usa un bloque de script en cada iteración (como se indica,% Number
es el equivalente de% { $_.Number }
).
Nota: Curiosamente, ejecutar la misma prueba en PowerShell Core v6.0.2 en macOS produce una clasificación diferente (aunque la observación básica acerca de las canalizaciones es mucho más lenta): la enumeración de miembros es más rápida e incluso la .ForEach()
basada en bloques de secuencias de .ForEach()
solución es notablemente más rápida que Select-Object -ExpandProperty
.
Código fuente para las pruebas :
# Number of input objects to create.
$count = 1e5 # 100,000
# Create sample input objects.
$objects = 1..$count | % { [pscustomobject] @{ Number = $_ } }
# An array of script blocks with the various approaches.
$approaches = { $objects | Select-Object -ExpandProperty Number },
{ $objects | % Number },
{ $objects.ForEach(''Number'') },
{ $objects.ForEach({ $_.Number }) },
{ $objects.Number }
# Time the approaches and sort them by execution time (fastest first):
$approaches | % {
$scriptBlock = $_
Measure-Command $scriptBlock |
Select-Object @{ n=''Approach''; e = { $scriptBlock.ToString() } } , Ticks
} |
Sort-Object Ticks |
Format-Table Approach, @{ n = ''Seconds''; e = { ''{0:N3}'' -f ($_.Ticks / 1e7) } }