Selecione os valores de uma propriedade em todos os objects de uma matriz no PowerShell

Digamos que temos uma matriz de objects $ objects. Digamos que esses objects tenham uma propriedade “Name”.

Isso é o que eu quero fazer

$results = @() $objects | %{ $results += $_.Name } 

Isso funciona, mas pode ser feito de uma maneira melhor?

Se eu fizer algo como:

  $results = objects | select Name 

$results é uma matriz de objects que possuem uma propriedade Name. Eu quero $ resultados para conter uma matriz de nomes.

Existe uma maneira melhor?

Eu acho que você pode ser capaz de usar o parâmetro ExpandProperty de Select-Object .

Por exemplo, para obter a lista do diretório atual e apenas exibir a propriedade Name, uma faria o seguinte:

 ls | select -Property Name 

Isso ainda está retornando objects DirectoryInfo ou FileInfo. Você sempre pode inspecionar o tipo que vem através do pipeline, canalizando para Get-Member (alias gm ).

 ls | select -Property Name | gm 

Então, para expandir o object para o tipo de propriedade que você está procurando, você pode fazer o seguinte:

 ls | select -ExpandProperty Name 

No seu caso, você pode apenas fazer o seguinte para ter uma variável como uma matriz de strings, onde as strings são a propriedade Name:

 $objects = ls | select -ExpandProperty Name 

Como uma solução ainda mais fácil, você poderia simplesmente usar:

 $results = $objects.Name 

Que deve preencher $results com uma matriz de todos os valores de propriedade ‘Name’ dos elementos em $objects .

Para complementar as respostas preexistentes e úteis, com orientação sobre quando usar essa abordagem e uma comparação de desempenho .

  • Fora de um pipeline, use:

      $ objects .  Nome 

    (PSv3 +), como demonstrado na resposta de rageandqq , que é sintaticamente mais simples e muito mais rápida .

    • Acessar uma propriedade no nível da coleção para obter os valores de seus membros como uma matriz é chamado de enumeração de membros e é um recurso PSv3 + ;
    • Alternativamente, no PSv2 , use a instrução foreach , cuja saída você também pode atribuir diretamente a uma variável:
        $ results = foreach ($ obj em $ objects) {$ obj.Name} 
    • Tradeoffs :
      • Tanto a coleção de input quanto a matriz de saída devem caber na memory como um todo .
      • Se a coleção de input for ela mesma o resultado de um comando (pipeline) (por exemplo, (Get-ChildItem).Name ), esse comando deve primeiro ser executado até a conclusão antes que os elementos da matriz resultante possam ser acessados.
  • Em um pipeline em que o resultado deve ser processado ou os resultados não se ajustam à memory como um todo, use:

      $ objects |  Select-Object -ExpandProperty Name 

    • A necessidade de -ExpandProperty é explicada na resposta de Scott Saad .
    • Você obtém os benefícios habituais do pipeline de processamento um por um, que normalmente produz saída imediatamente e mantém o uso da memory constante (a menos que você finalmente colete os resultados na memory).
    • Tradeoff :
      • O uso do pipeline é relativamente lento .

Para pequenas collections de input (arrays), você provavelmente não notará a diferença e, especialmente na linha de comando, às vezes ser capaz de digitar o comando facilmente é mais importante.


Aqui está uma alternativa fácil de digitar , que, no entanto, é a abordagem mais lenta ; ele usa a syntax simplificada ForEach-Object chamada de instrução de operação (novamente, PSv3 +):; por exemplo, a seguinte solução PSv3 + é fácil de append a um comando existente:

 $objects | % Name # short for: $objects | ForEach-Object -Process { $_.Name } 

Por uma questão de completude: O pouco conhecido método de coleta PSv4 + .ForEach() é outra alternativa :

 # By property name (string): $objects.ForEach('Name') # By script block (much slower): $objects.ForEach({ $_.Name }) 
  • Essa abordagem é semelhante à enumeração de membros , com as mesmas compensações, exceto que a lógica de pipeline não é aplicada; é ligeiramente mais lento , embora ainda visivelmente mais rápido que o oleoduto.

  • Para extrair um único valor de propriedade por nome (argumento string ), esta solução está no mesmo nível da enumeração de membros (embora a última seja sintaticamente mais simples).

  • A variante do bloco de script , embora muito mais lenta, permite transformações arbitrárias; é uma alternativa mais rápida – all-in-memory-at-once – ao cmdlet ForEach-Object baseado em pipeline .


Comparando o desempenho das várias abordagens

Aqui estão exemplos de temporizações para as várias abordagens, com base em uma coleta de input de 100,000 objects , com uma média de 100 execuções; os números absolutos não são importantes e variam com base em muitos fatores, mas devem dar a você uma sensação de desempenho 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 
  • A solução de método de coleta baseada em enumeração / propriedade-membro é mais rápida por um fator de 10+ do que a solução baseada em pipeline mais rápida.

  • A solução de declaração foreach é cerca de 2,5 mais lenta, mas ainda cerca de 4-5 vezes mais rápida que a solução de pipeline mais rápida.

  • O uso de um bloco de script com a solução de método de coleta ( .ForEach({ ... } ) reduz drasticamente as coisas, de modo que esteja praticamente no mesmo nível da solução baseada em pipeline mais rápida ( Select-Object -ExpandProperty ).

  • % Number ( ForEach-Object Number ), curiosamente, tem um desempenho pior, embora % Number seja o equivalente conceitual de % { $_.Number } ).


Código fonte para os testes :

Nota: Faça o download da function Time-Command desta Gist para executar estes testes.

 $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