PowerShell'de bir dizinin tüm nesnelerindeki bir özelliğin değerlerini seçin


135

Diyelim ki bir dizi nesnemiz var $ nesneler. Bu nesnelerin bir "Ad" özelliğine sahip olduğunu varsayalım.

Bu yapmak istediğim şey

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

Bu işe yarıyor, ancak daha iyi bir şekilde yapılabilir mi?

Şöyle bir şey yaparsam:

 $results = objects | select Name

$resultsName özelliğine sahip nesneler dizisidir. $ Results'un bir İsimler dizisi içermesini istiyorum.

Daha iyi bir yol var mı?


4
Eksiksizlik açısından, orijinal kodunuzdan "+ =" işaretini kaldırabilirsiniz, böylece foreach yalnızca Ad: 'ı seçer $results = @($objects | %{ $_.Name }). Bu, bazen komut satırına yazmak için daha uygun olabilir, ancak bence Scott'ın cevabının genellikle daha iyi olduğunu düşünüyorum .
İmparator XLII

1
@EmperorXLII: İyi bir nokta ve PSv3 + ile şunları basitleştirebilirsiniz:$objects | % Name
mklement0

Yanıtlar:


212

Sanırım ExpandPropertyparametresini kullanabileceğinizi düşünüyorum Select-Object.

Örneğin, geçerli dizinin listesini almak ve yalnızca Name özelliğinin görüntülenmesini sağlamak için aşağıdakilerden biri yapılır:

ls | select -Property Name

Bu hala DirectoryInfo veya FileInfo nesnelerini döndürüyor. Get-Member (diğer ad gm) ile boru hattından geçen türü her zaman inceleyebilirsiniz .

ls | select -Property Name | gm

Dolayısıyla, nesneyi aradığınız özellik türüne genişletmek için aşağıdakileri yapabilirsiniz:

ls | select -ExpandProperty Name

Sizin durumunuzda, bir değişkenin bir dizi dizisi olması için aşağıdakileri yapabilirsiniz, burada dizeler Name özelliğidir:

$objects = ls | select -ExpandProperty Name

73

Daha da kolay bir çözüm olarak şunları kullanabilirsiniz:

$results = $objects.Name

İçerisindeki $resultsöğelerin tüm 'Ad' özellik değerlerinin bir dizisi ile doldurulmalıdır $objects.


Bunun işe yaramadığını unutmayın Exchange Management Shell. Exchange'i kullanırken şunu kullanmamız gerekiyor$objects | select -Property Propname, OtherPropname
Bassie

2
@Bassie: Üyelerinin değerlerini bir dizi olarak almak için koleksiyon düzeyinde bir özelliğe erişmeye üye numaralandırma adı verilir ve bir PSv3 + özelliğidir ; Muhtemelen, Exchange Yönetim Kabuğunuz PSv2'dir.
mklement 0

32

Önceden var olan yararlı yanıtları, hangi yaklaşımın ne zaman kullanılacağına ve bir performans karşılaştırmasına rehberlik ederek tamamlamak .

  • Bir ardışık düzen dışında , (PSv3 +) kullanın:

    $ nesneler . ad
    rageandqq'ın cevabında gösterildiği gibi , hem sözdizimsel olarak daha basit hem de çok daha hızlı .

    • Üyelerinin değerlerini bir dizi olarak almak için koleksiyon düzeyinde bir özelliğe erişmeye üye numaralandırma denir ve bir PSv3 + özelliğidir.
    • Alternatif olarak, PSv2'de , çıktısını doğrudan bir değişkene atayabileceğiniz foreach ifadeyi kullanın :
      $ sonuçlar = foreach ($ nesnelerde $ obj) {$ obj.Name}
    • Ödünleşimler :
      • Hem girdi toplama hem de çıktı dizisi bir bütün olarak belleğe sığmalıdır .
      • Girdi koleksiyonunun kendisi bir komutun (ardışık düzen ) (örneğin (Get-ChildItem).Name) sonucuysa , bu komut , ortaya çıkan dizinin elemanlarına erişilmeden önce tamamlanana kadar çalıştırılmalıdır .
  • Sonucun daha fazla işlenmesi gereken veya sonuçların bir bütün olarak belleğe sığmadığı bir işlem hattında , şunu kullanın:

    $ nesneler | Select-Object -ExpandProperty Name

    • İhtiyaç -ExpandProperty, Scott Saad'ın cevabında açıklanıyor .
    • Tek tek işlemenin olağan ardışık düzeni avantajlarını elde edersiniz, bu genellikle çıktıyı hemen üretir ve bellek kullanımını sabit tutar (sonuçta sonuçları bellekte toplamadığınız sürece).
    • Takas :
      • Kullanımı boru hattının nispeten olduğunu yavaş .

İçin küçük giriş koleksiyonları (diziler), muhtemelen fark olmaz bazen komutu yazın edememek, özellikle komut satırında, ve kolayca daha önemlidir.


İşte yazması kolay bir alternatif , ancak en yavaş yaklaşım ; işlem ifadesi olarak adlandırılan basitleştirilmiş ForEach-Objectsözdizimi kullanır (yine, PSv3 +):; örneğin, aşağıdaki PSv3 + çözümünün mevcut bir komuta eklenmesi kolaydır:

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

Bütünlük adına: az bilinen PSv4 + .ForEach() dizi yöntem , ele alınan daha comprehensivel Bu makalede , bir diğer bir alternatif :

# By property name (string):
$objects.ForEach('Name')

# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
  • Bu yaklaşım üye sayım benzer olduğu boru hattı mantığı olması dışında, aynı ödünleşmeler ile değil uygulamalı; öyle marjinal yavaş hızlı boru hattı daha belirgin olsa yine de.

  • Tek bir özellik değeri ayıklanması için isim ( dize (ikinci sözdizimsel daha basit olsa) argüman), bu çözüm üye sayım ile eşit olduğunu.

  • Komut blok varyantı , keyfi sağlar dönüşümler ; bu, ardışık ForEach-Object düzen tabanlı cmdlet ( %) 'e göre daha hızlı - hepsi bir arada bellekte - bir alternatiftir .


Çeşitli yaklaşımların performansını karşılaştırma

Burada, 10 çalıştırmada ortalaması alınan nesnelerin girdi koleksiyonuna dayalı çeşitli yaklaşımlar için örnek zamanlamalar verilmiştir ; mutlak sayılar önemli değildir ve birçok faktöre bağlı olarak değişir, ancak size göreceli bir performans hissi vermelidir (zamanlamalar tek çekirdekli bir Windows 10 VM'den gelir:10,000

Önemli

  • Göreli performans, girdi nesnelerinin normal .NET Türlerinin örnekleri (örneğin, çıktı olarak Get-ChildItem) veya [pscustomobject]örnekler (ör Convert-FromCsv. Çıktı olarak ) olmasına bağlı olarak değişir .
    Bunun nedeni, [pscustomobject]özelliklerin PowerShell tarafından dinamik olarak yönetilmesi ve bunlara (statik olarak tanımlanmış) normal bir .NET türünün normal özelliklerinden daha hızlı erişebilmesidir. Her iki senaryo da aşağıda ele alınmıştır.

  • Testler, salt özellik çıkarma performansına odaklanmak için, zaten bellekte tam olan koleksiyonları girdi olarak kullanır. Girdi olarak akışlı bir cmdlet / işlev çağrısı ile, bu çağrı içinde harcanan zaman harcanan zamanın çoğunu açıklayabileceğinden, performans farklılıkları genellikle çok daha az belirgin olacaktır.

  • Kısalık %sağlamak amacıyla ForEach-Objectcmdlet için takma ad kullanılır .

Hem normal .NET türü hem de [pscustomobject]girdisi için geçerli genel sonuçlar :

  • Üye numaralandırması ( $collection.Name) ve foreach ($obj in $collection)çözümler, en hızlı ardışık düzen tabanlı çözümden 10 kat daha hızlı veya daha hızlıdır.

  • Şaşırtıcı bir şekilde, % Nameşundan çok daha kötü performans gösterir % { $_.Name }- bu GitHub sorununa bakın .

  • PowerShell Core, burada sürekli olarak Windows Powershell'den daha iyi performans gösterir.

Normal .NET türleriyle zamanlamalar :

  • PowerShell Core v7.0.0-önizleme.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

Sonuç:

  • PowerShell içinde Çekirdek , .ForEach('Name')açıkça daha iyi performans .ForEach({ $_.Name }). Windows PowerShell'de, merakla, ikincisi daha hızlıdır, ancak çok az da olsa.

[pscustomobject]Örneklerle zamanlamalar :

  • PowerShell Core v7.0.0-önizleme.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

Sonuç:

  • İle nasıl Not [pscustomobject]girişi .ForEach('Name')uzak geride tarafından komut blok varyantını esaslı, .ForEach({ $_.Name }).

  • Benzer şekilde, [pscustomobject]girdi, ardışık düzen tabanlı Select-Object -ExpandProperty Nameolanı Windows PowerShell'de neredeyse eşit .ForEach({ $_.Name }), ancak PowerShell Core'da hala yaklaşık% 50 daha yavaş hale getirir .

  • Kısaca: garip haricinde % Nameolan [pscustomobject]özelliklerini nasıl atıfta dize dayalı yöntemlerle scriptblock bazlı olanları daha iyi performans.


Testler için kaynak kodu :

Not:

  • Fonksiyonunu indirin Time-Commandgelen bu Gist bu testler.

  • Bunun yerine örneklerle ölçmek $useCustomObjectInputiçin ayarlayın .$true[pscustomobject]

$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*

1

Dikkat, üye numaralandırma yalnızca koleksiyonun kendisinin aynı ada sahip bir üyesi yoksa çalışır. Dolayısıyla, bir dizi FileInfo nesneniz varsa, kullanarak bir dizi dosya uzunluğu elde edemezsiniz.

 $files.length # evaluates to array length

Ve "açıkçası" demeden önce, şunu düşünün. Kapasite özelliğine sahip bir dizi nesneniz varsa

 $objarr.capacity

uğratacak işler ince SÜRECE örneğin aslında vardı objarr $ değil [Dizi] ancak, bir [ArrayList]. Bu nedenle üye numaralandırmayı kullanmadan önce koleksiyonunuzun bulunduğu kara kutunun içine bakmanız gerekebilir.

(Moderatörlere not: Bu, rageandqq'ın cevabı üzerine bir yorum olmalı, ancak henüz yeterince itibarım yok.)


Bu iyi bir nokta; bu GitHub özellik isteği , üye numaralandırması için ayrı bir sözdizimi ister. Ad çakışmaları için geçici çözüm, .ForEach()dizi yöntemini aşağıdaki gibi kullanmaktır :$files.ForEach('Length')
mklement0
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.