Para complementar las respuestas útiles y preexistentes con orientación sobre cuándo usar qué enfoque y una comparación de rendimiento .
Fuera de una tubería, use (PSv3 +):
$ objetos . Nombre
como se demostró en la respuesta de rageandqq , que es sintácticamente más simple y mucho más rápido .
En una tubería donde el resultado debe procesarse más o los resultados no caben en la memoria como un todo, use:
$ objetos | Select-Object -ExpandProperty Name
- La necesidad
-ExpandProperty
se explica en la respuesta de Scott Saad .
- Obtiene los beneficios de canalización habituales del procesamiento uno por uno, que generalmente produce resultados de inmediato y mantiene el uso constante de la memoria (a menos que finalmente recopile los resultados en la memoria de todos modos).
- Compensación :
- El uso de la tubería es relativamente lento .
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 ; utiliza una ForEach-Object
sintaxis simplificada llamada declaració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 método de matriz PSv4 +.ForEach()
poco conocido , más completo que se analiza en este artículo , es otra alternativa :
# By property name (string):
$objects.ForEach('Name')
# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
Este enfoque es similar a la enumeración de miembros , con las mismas compensaciones, excepto que no se aplica la lógica de canalización ; es marginalmente más lento , aunque todavía notablemente más rápido que la tubería.
Para extraer un solo valor de propiedad por nombre ( argumento de cadena ), esta solución está a la par con la enumeración de miembros (aunque esta última es sintácticamente más simple).
La variante de bloque de script permite transformaciones arbitrarias ; es una alternativa más rápida, todo en memoria a la vez, al ForEach-Object
cmdlet basado en canalización ( %
) .
Comparar el rendimiento de los distintos enfoques.
Aquí hay ejemplos de tiempos para los diversos enfoques, basados en una colección de entrada de 10,000
objetos , promediados en 10 ejecuciones; los números absolutos no son importantes y varían en función de muchos factores, pero deberían darle una sensación de rendimiento relativo (los tiempos provienen de una VM de Windows 10 de un solo núcleo:
Importante
El rendimiento relativo varía en función de si los objetos de entrada son instancias de tipos .NET regulares (por ejemplo, como resultado Get-ChildItem
) o [pscustomobject]
instancias (por ejemplo, como resultado Convert-FromCsv
).
La razón es que [pscustomobject]
PowerShell administra dinámicamente las propiedades y puede acceder a ellas más rápidamente que las propiedades regulares de un tipo .NET regular (definido estáticamente). Ambos escenarios se cubren a continuación.
Las pruebas utilizan colecciones ya en memoria llena como entrada, para enfocarse en el rendimiento de extracción de propiedad pura. Con una llamada de cmdlet / función de transmisión como entrada, las diferencias de rendimiento generalmente serán mucho menos pronunciadas, ya que el tiempo pasado dentro de esa llamada puede representar la mayor parte del tiempo dedicado.
Para abreviar, %
se usa alias para el ForEach-Object
cmdlet.
Conclusiones generales , aplicables tanto al tipo regular de .NET como a la [pscustomobject]
entrada:
La enumeración de miembros ( $collection.Name
) y las foreach ($obj in $collection)
soluciones son, con mucho, las más rápidas , por un factor de 10 o más más rápido que la solución basada en canalizaciones más rápida.
Sorprendentemente, % Name
funciona mucho peor que % { $_.Name }
: vea este problema de GitHub .
PowerShell Core supera constantemente a Windows Powershell aquí.
Tiempos con tipos regulares de .NET :
- PowerShell Core v7.0.0-preview.3
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.005
1.06 foreach($o in $objects) { $o.Name } 0.005
6.25 $objects.ForEach('Name') 0.028
10.22 $objects.ForEach({ $_.Name }) 0.046
17.52 $objects | % { $_.Name } 0.079
30.97 $objects | Select-Object -ExpandProperty Name 0.140
32.76 $objects | % Name 0.148
- Windows PowerShell v5.1.18362.145
Comparing property-value extraction methods with 10000 input objects, averaged over 10 runs...
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.012
1.32 foreach($o in $objects) { $o.Name } 0.015
9.07 $objects.ForEach({ $_.Name }) 0.105
10.30 $objects.ForEach('Name') 0.119
12.70 $objects | % { $_.Name } 0.147
27.04 $objects | % Name 0.312
29.70 $objects | Select-Object -ExpandProperty Name 0.343
Conclusiones:
- En PowerShell Core ,
.ForEach('Name')
claramente supera el rendimiento .ForEach({ $_.Name })
. En Windows PowerShell, curiosamente, este último es más rápido, aunque solo marginalmente.
Tiempos con [pscustomobject]
instancias :
- PowerShell Core v7.0.0-preview.3
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.006
1.11 foreach($o in $objects) { $o.Name } 0.007
1.52 $objects.ForEach('Name') 0.009
6.11 $objects.ForEach({ $_.Name }) 0.038
9.47 $objects | Select-Object -ExpandProperty Name 0.058
10.29 $objects | % { $_.Name } 0.063
29.77 $objects | % Name 0.184
- Windows PowerShell v5.1.18362.145
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.008
1.14 foreach($o in $objects) { $o.Name } 0.009
1.76 $objects.ForEach('Name') 0.015
10.36 $objects | Select-Object -ExpandProperty Name 0.085
11.18 $objects.ForEach({ $_.Name }) 0.092
16.79 $objects | % { $_.Name } 0.138
61.14 $objects | % Name 0.503
Conclusiones:
Nota cómo con [pscustomobject]
entrada .ForEach('Name')
por Supera con mucho, el script del bloque variante basa, .ForEach({ $_.Name })
.
Del mismo modo, la [pscustomobject]
entrada hace que la canalización sea Select-Object -ExpandProperty Name
más rápida, en Windows PowerShell prácticamente a la par .ForEach({ $_.Name })
, pero en PowerShell Core todavía un 50% más lento.
En resumen: con la extraña excepción de % Name
, con [pscustomobject]
los métodos basados en cadenas para hacer referencia a las propiedades superan a las basadas en scriptblock.
Código fuente para las pruebas :
Nota:
$count = 1e4 # max. input object count == 10,000
$runs = 10 # number of runs to average
# Note: Using [pscustomobject] instances rather than instances of
# regular .NET types changes the performance characteristics.
# Set this to $true to test with [pscustomobject] instances below.
$useCustomObjectInput = $false
# Create sample input objects.
if ($useCustomObjectInput) {
# Use [pscustomobject] instances.
$objects = 1..$count | % { [pscustomobject] @{ Name = "$foobar_$_"; Other1 = 1; Other2 = 2; Other3 = 3; Other4 = 4 } }
} else {
# Use instances of a regular .NET type.
# Note: The actual count of files and folders in your home dir. tree
# may be less than $count
$objects = Get-ChildItem -Recurse $HOME | Select-Object -First $count
}
Write-Host "Comparing property-value extraction methods with $($objects.Count) input objects, averaged over $runs runs..."
# An array of script blocks with the various approaches.
$approaches = { $objects | Select-Object -ExpandProperty Name },
{ $objects | % Name },
{ $objects | % { $_.Name } },
{ $objects.ForEach('Name') },
{ $objects.ForEach({ $_.Name }) },
{ $objects.Name },
{ foreach($o in $objects) { $o.Name } }
# Time the approaches and sort them by execution time (fastest first):
Time-Command $approaches -Count $runs | Select Factor, Command, Secs*
$results = @($objects | %{ $_.Name })
. Esto puede ser más conveniente para escribir en la línea de comando a veces, aunque creo que la respuesta de Scott es generalmente mejor.