Pilih nilai satu properti di semua objek array di PowerShell

134

Katakanlah kita memiliki array objek $ objek. Katakanlah benda-benda ini memiliki properti "Nama".

Ini yang ingin saya lakukan

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

Ini bekerja, tetapi bisakah itu dilakukan dengan cara yang lebih baik?

Jika saya melakukan sesuatu seperti:

 $results = objects | select Name

$resultsadalah larik objek yang memiliki properti Nama. Saya ingin $ hasil mengandung array Nama.

Apakah ada cara yang lebih baik?

Sylvain Reverdy
sumber
4
Hanya untuk kelengkapan, Anda juga bisa menghapus "+ =" dari kode asli Anda, sehingga foreach hanya memilih Nama: $results = @($objects | %{ $_.Name }). Ini bisa lebih mudah untuk mengetik di baris perintah di kali, meskipun saya pikir jawaban Scott umumnya lebih baik.
Kaisar XLII
1
@EmperorXLII: Poin bagus, dan di PSv3 + Anda bahkan dapat menyederhanakan:$objects | % Name
mklement0

Jawaban:

212

Saya pikir Anda mungkin dapat menggunakan ExpandPropertyparameter Select-Object.

Sebagai contoh, untuk mendapatkan daftar direktori saat ini dan hanya memiliki properti Nama yang ditampilkan, orang akan melakukan hal berikut:

ls | select -Property Name

Ini masih mengembalikan objek DirectoryInfo atau FileInfo. Anda selalu dapat memeriksa jenis yang datang melalui pipa dengan memipet ke Get-Member (alias gm).

ls | select -Property Name | gm

Jadi, untuk memperluas objek menjadi tipe properti yang Anda lihat, Anda dapat melakukan hal berikut:

ls | select -ExpandProperty Name

Dalam kasus Anda, Anda bisa melakukan yang berikut untuk membuat variabel menjadi array string, di mana string adalah properti Name:

$objects = ls | select -ExpandProperty Name
Scott Saad
sumber
73

Sebagai solusi yang lebih mudah, Anda bisa menggunakan:

$results = $objects.Name

Yang harus diisi $resultsdengan larik semua nilai properti 'Nama' elemen di $objects.

rageandqq
sumber
Perhatikan bahwa ini tidak berfungsi di Exchange Management Shell. Saat menggunakan Exchange, kita perlu menggunakan$objects | select -Property Propname, OtherPropname
Bassie
2
@Bassie: Mengakses properti di tingkat pengumpulan untuk mendapatkan nilai anggotanya sebagai array disebut penghitungan anggota dan merupakan fitur PSv3 + ; mungkin, Exchange Management Shell Anda adalah PSv2.
mklement0
32

Untuk melengkapi jawaban yang sudah ada sebelumnya, jawaban yang bermanfaat dengan panduan kapan harus menggunakan pendekatan mana dan perbandingan kinerja .

  • Di luar pipa, gunakan (PSv3 +):

    $ objek . Nama
    seperti yang ditunjukkan dalam jawaban rageandqq , yang secara sintaksis lebih sederhana dan lebih cepat .

    • Mengakses properti di tingkat pengumpulan untuk mendapatkan nilai anggotanya sebagai array disebut penghitungan anggota dan merupakan fitur PSv3 +.
    • Atau, di PSv2 , gunakan foreach pernyataan , yang outputnya dapat Anda tetapkan langsung ke variabel:
      $ results = foreach ($ obj dalam $ objects) {$ obj.Name}
    • Pengorbanan :
      • Kedua koleksi input dan output array harus masuk ke dalam memori secara keseluruhan .
      • Jika pengumpulan input itu sendiri adalah hasil dari suatu perintah (pipeline) (misalnya, (Get-ChildItem).Name), perintah itu harus terlebih dahulu dijalankan sampai selesai sebelum elemen array yang dihasilkan dapat diakses.
  • Dalam pipa yang hasilnya harus diproses lebih lanjut atau hasilnya tidak sesuai dengan memori secara keseluruhan, gunakan:

    $ objek | Select-Object -ExpandProperty Name

    • Kebutuhan untuk -ExpandPropertydijelaskan dalam jawaban Scott Saad .
    • Anda mendapatkan manfaat pipa biasa dari pemrosesan satu-per-satu, yang biasanya menghasilkan output segera dan menjaga penggunaan memori konstan (kecuali jika Anda akhirnya mengumpulkan hasilnya dalam memori pula).
    • Pengorbanan :
      • Penggunaan pipa relatif lambat .

Untuk koleksi input kecil (array), Anda mungkin tidak akan melihat perbedaannya , dan, terutama pada baris perintah, kadang-kadang bisa mengetik perintah dengan mudah lebih penting.


Berikut ini adalah alternatif yang mudah diketik , yang, bagaimanapun, merupakan pendekatan paling lambat ; menggunakan sintaks yang disederhanakan yang ForEach-Objectdisebut pernyataan operasi (sekali lagi, PSv3 +):; mis., solusi PSv3 + berikut ini mudah ditambahkan ke perintah yang ada:

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

Demi kelengkapan: Metode array PSv4 + yang kurang.ForEach() dikenal , lebih lengkap dibahas dalam artikel ini , adalah alternatif lain :

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

# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
  • Pendekatan ini mirip dengan penghitungan anggota , dengan pengorbanan yang sama, kecuali bahwa logika pipa tidak diterapkan; ini sedikit lebih lambat , meskipun masih terasa lebih cepat daripada pipa.

  • Untuk mengekstraksi nilai properti tunggal dengan nama ( argumen string ), solusi ini setara dengan enumerasi anggota (meskipun yang terakhir secara sintaksis lebih sederhana).

  • The Script-blok varian , memungkinkan sewenang-wenang transformasi ; ini adalah alternatif yang lebih cepat - all-in-memory-at-sekaligus - ke ForEach-Object cmdlet berbasis-pipa ( %) .


Membandingkan kinerja berbagai pendekatan

Berikut adalah contoh waktu untuk berbagai pendekatan, berdasarkan pada kumpulan input 10,000objek , rata-rata di 10 run; angka absolut tidak penting dan bervariasi berdasarkan banyak faktor, tetapi seharusnya memberi Anda rasa kinerja relatif (timing berasal dari satu-core Windows 10 VM:

Penting

  • Kinerja relatif bervariasi berdasarkan pada apakah objek input adalah instance dari .NET Type (misalnya, sebagai output oleh Get-ChildItem) atau [pscustomobject]instance (misalnya, sebagai output oleh Convert-FromCsv).
    Alasannya adalah bahwa [pscustomobject]properti dikelola secara dinamis oleh PowerShell, dan itu dapat mengaksesnya lebih cepat daripada properti biasa dari tipe .NET biasa (yang ditentukan secara statis). Kedua skenario dibahas di bawah ini.

  • Pengujian menggunakan koleksi yang sudah ada dalam memori-dalam-penuh sebagai input, sehingga dapat fokus pada kinerja ekstraksi properti murni. Dengan cmdlet streaming / panggilan fungsi sebagai input, perbedaan kinerja umumnya akan jauh lebih sedikit, karena waktu yang dihabiskan di dalam panggilan itu mungkin merupakan bagian dari sebagian besar waktu yang dihabiskan.

  • Untuk singkatnya, alias %digunakan untuk ForEach-Objectcmdlet.

Kesimpulan umum , berlaku untuk tipe dan [pscustomobject]input .NET reguler :

  • Penghitungan anggota ( $collection.Name) dan foreach ($obj in $collection)solusi sejauh ini tercepat , dengan faktor 10 atau lebih cepat daripada solusi berbasis pipa tercepat.

  • Anehnya, % Nameperformanya jauh lebih buruk daripada % { $_.Name }- lihat masalah GitHub ini .

  • PowerShell Core secara konsisten mengungguli Windows Powershell di sini.

Pengaturan waktu dengan tipe .NET biasa :

  • 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

Kesimpulan:

  • Di PowerShell Core , .ForEach('Name')jelas mengungguli .ForEach({ $_.Name }). Di Windows PowerShell, anehnya, yang terakhir lebih cepat, meskipun hanya sedikit.

Pengaturan waktu dengan [pscustomobject]instance :

  • 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

Kesimpulan:

  • Perhatikan bagaimana dengan [pscustomobject]input yang .ForEach('Name')jauh melebihi varian berbasis blok script .ForEach({ $_.Name }),.

  • Demikian pula, [pscustomobject]input membuat berbasis pipa Select-Object -ExpandProperty Namelebih cepat, di Windows PowerShell hampir setara .ForEach({ $_.Name }), tetapi di PowerShell Core masih sekitar 50% lebih lambat.

  • Singkatnya: Dengan pengecualian aneh % Name, dengan [pscustomobject]metode berbasis string untuk mereferensikan properti mengungguli yang berbasis scriptblock.


Kode sumber untuk tes :

catatan:

  • Unduh fungsi Time-Commanddari Intisari ini untuk menjalankan tes ini.

  • Tetapkan $useCustomObjectInputuntuk $truemengukur dengan [pscustomobject]instance sebagai gantinya.

$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*
mklement0
sumber
1

Perhatian, penghitungan anggota hanya berfungsi jika koleksi itu sendiri tidak memiliki anggota dengan nama yang sama. Jadi jika Anda memiliki larik objek FileInfo, Anda tidak bisa mendapatkan larik panjang file dengan menggunakan

 $files.length # evaluates to array length

Dan sebelum Anda mengatakan "jelas sekali", pertimbangkan ini. Jika Anda memiliki array objek dengan properti kapasitas, maka

 $objarr.capacity

akan berfungsi dengan baik KECUALI $ objarr sebenarnya bukan [Array] tetapi, misalnya, [ArrayList]. Jadi sebelum menggunakan enumerasi anggota Anda mungkin harus melihat ke dalam kotak hitam yang berisi koleksi Anda.

(Catatan untuk moderator: ini harus menjadi komentar pada jawaban rageandqq tetapi saya belum memiliki reputasi yang cukup.)

Uber Kluger
sumber
Ini poin yang bagus; permintaan fitur GitHub ini meminta sintaks terpisah untuk enumerasi anggota. Solusi untuk tabrakan nama adalah dengan menggunakan .ForEach()metode array sebagai berikut:$files.ForEach('Length')
mklement0