Selecione os valores de uma propriedade em todos os objetos de uma matriz em PowerShell
$results = @()
$objects | %{ $results += $_.Name }
Isto funciona, mas pode ser feito de uma forma melhor?
Se eu fizer algo como:
$results = objects | select Name
$results
é uma matriz de objetos com uma propriedade de nome. Quero que os resultados contenham uma série de nomes.
3 answers
Eu acho que você pode ser capaz de usar o parâmetro ExpandProperty
de Select-Object
.
Por exemplo, para obter a lista da pasta actual e apenas ter a propriedade do nome apresentada, faria-se o seguinte:
ls | select -Property Name
Isto ainda está a devolver os objectos DirectoryInfo ou FileInfo. Você pode sempre inspecionar o tipo que vem através do oleoduto através de piping para Get-Member (Também conhecido por gm
).
ls | select -Property Name | gm
Então, para expandir o objeto para ser o do tipo de propriedade que você é olhando, você pode fazer o seguinte:
ls | select -ExpandProperty Name
No seu caso, pode apenas fazer o seguinte para que uma variável seja um array de strings, onde as strings são a propriedade Nome:
$objects = ls | select -ExpandProperty Name
Como uma solução ainda mais fácil, você poderia apenas usar:
$results = $objects.Name
Que deve preencher $results
com uma matriz de todos os valores de propriedade 'Nome' dos elementos em $objects
.
Para complementar as respostas pré-existentes e úteis com a orientação de quando usar qual abordageme uma comparação de desempenho .
-
Fora de um pipeline, use:
$objects.Name
(PSv3+), como demonstrado no rageandqq a resposta, que é sintaticamente mais simples e muito mais rápido.- aceder a uma propriedade no nível para obter os valores dos seus membros como um matriz {[34] } é chamada enumeração dos membros e é uma característica PSv3+ ;
- em alternativa, emPSv2 , utilize o
foreach
instrução , cujo resultado Poderá também atribuir directamente a uma variável:$results = foreach ($obj in $objects) { $obj.Name }
-
Tradeoffs:
- tanto o conjunto de dados de entrada como de saída deve caber na memória como um todo.
- se a colecção de dados for ela própria o resultado de um comando (pipeline) (por exemplo,
(Get-ChildItem).Name
), que comando deve primeiro executar até completar antes que os elementos da matriz resultante possam ser acessados.
-
Num oleoduto quando o resultado deve ser processado ou os resultados não se encaixam na memória como um todo, use:
$objects | Select-Object -ExpandProperty Name
- A necessidade de
-ExpandProperty
é explicada na resposta de Scott Saad .
Recebes os benefícios habituais do processamento um a um., que normalmente produz saída imediatamente e mantém a memória usar constante (a menos que você finalmente coletar os resultados na memória de qualquer maneira).
-
Tradeoff:
- a utilização do oleoduto é comparativamente lenta .
Parasmall input collections (arrays), você provavelmente não vai notar a diferença, e, especialmente na linha de comandos, à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 ; utiliza sintaxe simplificada ForEach-Object
chamada de declaração de operação (mais uma vez, PSv3+):
; por exemplo, a seguinte solução PSv3+ é fácil de adicionar a um comando existente:
$objects | % Name # short for: $objects | ForEach-Object -Process { $_.Name }
Por uma questão de exaustividade: o pouco conhecido PSv4+ .ForEach()
método de recolha é mais um alternativa:
# By property name (string):
$objects.ForEach('Name')
# By script block (much slower):
$objects.ForEach({ $_.Name })
Esta abordagem é semelhante à enumeração de membros , com os mesmos tradeoffs, exceto que a lógica do oleoduto é Não aplicada; émarginalmente mais lenta , embora ainda visivelmente mais rápida do que o oleoduto.
Para extrair um único valor de propriedade por nome (string argumento), esta solução está a par com a enumeração de membros (embora esta última seja sintaticamente simplificado).
O script-bloco variante, embora muito mais lento, permite arbitrário transformações; é mais rápido tudo - em-memória-em-uma - vez- alternativa para o pipeline de base
ForEach-Object
cmdlet.
Comparar o desempenho das várias abordagens
Aqui estão cronogramas das amostras para as várias abordagens, com base numa colecção de dados de entrada de 100,000
objectos , em média ao longo de 100 corridas; os números absolutos não são importantes e variam com base em muitos factores, mas deve dar-lhe uma sensação de relativa desempenho:
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 do método de recolha baseado no nome da propriedade / enumeração de membros é mais rápida por um factor de 10+ do que a solução mais rápida baseada no oleoduto.
O
foreach
declaração a solução é cerca de 2.5 mais lenta, mas ainda cerca de 4-5 vezes mais rápida do que o gasoduto mais rápido solucao.O uso de um bloco de script com a solução de método de recolha (
.ForEach({ ... }
) atrasa as coisas dramaticamente, de modo que está praticamente a par com a solução mais rápida baseada em pipeline (Select-Object -ExpandProperty
).% Number
(ForEach-Object Number
), curiosamente, tem pior desempenho, embora% Number
seja o equivalente conceitual de% { $_.Number }
).
Código-fonte dos ensaios:
Nota: Função de Download Time-Command
de este Gist para fazer 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