Seleccione los valores de una propiedad en todos los objetos de una matriz en PowerShell

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 

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 .

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:

      $ objetos .  Nombre 

    (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 + ;
    • Alternativamente, en PSv2 , use la instrucción foreach , cuyo resultado también se puede asignar directamente a una variable:
        $ results = foreach ($ obj en $ objects) {$ obj.Name} 
    • 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:

      $ objetos |  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 .

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 syntax 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 poco conocido método de recolección PSv4 + .ForEach() 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 , con los mismos intercambios, excepto que la lógica de la tubería no se aplica; es marginalmente más lento , aunque aún notablemente más rápido que la tubería.

  • 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 en ForEach-Object .


Comparando el desempeño de los diferentes enfoques

Aquí se muestran los tiempos de muestreo para los diferentes enfoques, basados ​​en una colección de entrada de 100,000 objetos , promediados en 100 ejecuciones; 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 :

 Command FriendlySecs (100-run avg.) Factor ------- --------------------------- ------ $objects.ForEach('Number') 0.078 1.00 $objects.Number 0.079 1.02 foreach($o in $objects) { $o.Number } 0.188 2.42 $objects | Select-Object -ExpandProperty Number 0.881 11.36 $objects.ForEach({ $_.Number }) 0.925 11.93 $objects | % { $_.Number } 1.564 20.16 $objects | % Number 2.974 38.35 
  • La solución de método de recostackción basado en enumeración / nombre de propiedad es más rápida por un factor de 10+ que la solución más rápida basada en canalización.

  • La solución de statement foreach es aproximadamente 2.5 más lenta, pero aún así es aproximadamente 4-5 veces más rápida que la solución de canalización más rápida.

  • El uso de un bloque de script con la solución de método de recostackción ( .ForEach({ ... } ) ralentiza las cosas dramáticamente, de modo que está prácticamente a la par con la solución más rápida basada en canalización ( Select-Object -ExpandProperty ).

  • % Number ( ForEach-Object Number ), curiosamente, tiene el peor ForEach-Object Number , aunque % Number es el equivalente conceptual de % { $_.Number } ).


Código fuente para las pruebas :

Nota: Descargue la función Time-Command de este Gist para ejecutar estas pruebas.

 $count = 1e5 # input-object count (100,000) $runs = 100 # number of runs to average # 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 | % { $_.Number } }, { $objects.ForEach('Number') }, { $objects.ForEach({ $_.Number }) }, { $objects.Number }, { foreach($o in $objects) { $o.Number } } # Time the approaches and sort them by execution time (fastest first): Time-Command $approaches -Count $runs | Select Command, FriendlySecs*, Factor