Chọn các giá trị của một thuộc tính trên tất cả các đối tượng của một mảng trong PowerShell


134

Giả sử chúng ta có một mảng các đối tượng $ object. Giả sử những đối tượng này có thuộc tính "Tên".

Đây là những gì tôi muốn làm

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

Điều này hoạt động, nhưng nó có thể được thực hiện theo cách tốt hơn?

Nếu tôi làm một cái gì đó như:

 $results = objects | select Name

$resultslà một mảng các đối tượng có thuộc tính Name. Tôi muốn kết quả $ có chứa một mảng Tên.

Có cách nào tốt hơn?


4
Để hoàn thiện, bạn cũng có thể xóa "+ =" khỏi mã gốc của mình, để foreach chỉ chọn Tên : $results = @($objects | %{ $_.Name }). Điều này có thể thuận tiện hơn để gõ vào dòng lệnh đôi khi, mặc dù tôi nghĩ rằng câu trả lời của Scott nói chung là tốt hơn.
Hoàng đế XLII

1
@EmaohXLII: Điểm hay và trong PSv3 + bạn thậm chí có thể đơn giản hóa thành:$objects | % Name
mkuity0

Câu trả lời:


212

Tôi nghĩ rằng bạn có thể có thể sử dụng ExpandPropertytham số của Select-Object.

Ví dụ, để có được danh sách thư mục hiện tại và chỉ cần hiển thị thuộc tính Tên, người ta sẽ làm như sau:

ls | select -Property Name

Điều này vẫn trả về các đối tượng DirectoryInfo hoặc FileInfo. Bạn luôn có thể kiểm tra loại đi qua đường ống bằng cách chuyển sang Thành viên (bí danh gm).

ls | select -Property Name | gm

Vì vậy, để mở rộng đối tượng thành loại tài sản bạn đang xem, bạn có thể làm như sau:

ls | select -ExpandProperty Name

Trong trường hợp của bạn, bạn chỉ có thể thực hiện các thao tác sau để có một biến là một chuỗi các chuỗi, trong đó các chuỗi là thuộc tính Tên:

$objects = ls | select -ExpandProperty Name

73

Là một giải pháp thậm chí dễ dàng hơn, bạn chỉ có thể sử dụng:

$results = $objects.Name

Mà nên điền vào $resultsmột mảng của tất cả các giá trị thuộc tính 'Tên' của các phần tử trong $objects.


Lưu ý rằng điều này không hoạt động Exchange Management Shell. Khi sử dụng Exchange, chúng tôi cần sử dụng$objects | select -Property Propname, OtherPropname
Bassie

2
@Bassie: Truy cập một thuộc tính ở cấp độ bộ sưu tập để nhận các giá trị của các thành viên của nó dưới dạng một mảng được gọi là liệt kê thành viên và là một tính năng PSv3 + ; có lẽ, Exchange Management Shell của bạn là PSv2.
mkuity0

32

Để bổ sung cho các câu trả lời có sẵn, các câu trả lời hữu ích với hướng dẫn khi nào nên sử dụng phương pháp nàoso sánh hiệu suất .

  • Bên ngoài đường ống, sử dụng (PSv3 +):

    $ đối tượng . Tên
    như thể hiện trong câu trả lời của rageandqq , cả hai đều đơn giản hơn về mặt cú pháp và nhanh hơn nhiều .

    • Truy cập một thuộc tính ở cấp độ bộ sưu tập để nhận các giá trị của các thành viên dưới dạng một mảng được gọi là liệt kê thành viên và là một tính năng PSv3 +.
    • Ngoài ra, trong PSv2 , sử dụng foreach câu lệnh , có đầu ra mà bạn cũng có thể gán trực tiếp cho một biến:
      $ results = foreach ($ obj trong $ object) {$ obj.Name}
    • Đánh đổi :
      • Cả bộ sưu tập đầu vào và đầu ra mảng phải phù hợp với bộ nhớ như một toàn thể .
      • Nếu chính bộ sưu tập đầu vào là kết quả của một lệnh (đường ống) (ví dụ (Get-ChildItem).Name:), thì lệnh đó trước tiên phải chạy để hoàn thành trước khi các phần tử của mảng kết quả có thể được truy cập.
  • Trong một đường ống mà kết quả phải được xử lý thêm hoặc kết quả không phù hợp với toàn bộ bộ nhớ, hãy sử dụng:

    $ đối tượng | Chọn-Object -ExpandProperty Name

    • Sự cần thiết -ExpandPropertyđược giải thích trong câu trả lời của Scott Saad .
    • Bạn nhận được các lợi ích đường ống thông thường của xử lý từng cái một, thường tạo ra đầu ra ngay lập tức và giữ cho bộ nhớ sử dụng không đổi (trừ khi cuối cùng bạn vẫn thu thập kết quả trong bộ nhớ).
    • Đánh đổi :
      • Sử dụng các đường ống tương đối chậm .

Đối với các bộ sưu tập đầu vào nhỏ (mảng), có lẽ bạn sẽ không nhận thấy sự khác biệt và đặc biệt là trên dòng lệnh, đôi khi việc có thể gõ lệnh dễ dàng là quan trọng hơn.


Đây là một cách thay thế dễ gõ , tuy nhiên, đây là cách tiếp cận chậm nhất ; nó sử dụng cú pháp đơn giản hóa ForEach-Objectđược gọi là một câu lệnh hoạt động (một lần nữa, PSv3 +) :; ví dụ: giải pháp PSv3 + sau đây dễ dàng nối vào lệnh hiện có:

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

Vì mục đích hoàn chỉnh: Phương pháp mảng PSv4 +.ForEach() ít được biết đến , được hiểu nhiều hơn được thảo luận trong bài viết này , vẫnmột cách khác :

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

# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
  • Cách tiếp cận này là tương tự như liệt kê thành viên , với sự cân bằng tương tự, ngoại trừ rằng logic đường ống được không áp dụng; nó chậm hơn một chút , mặc dù vẫn nhanh hơn đáng kể so với đường ống.

  • Để trích xuất một giá trị thuộc tính duy nhất theo tên ( đối số chuỗi ), giải pháp này ngang bằng với liệt kê thành viên (mặc dù sau này đơn giản hơn về mặt cú pháp).

  • Các kịch bản-block biến , cho phép tùy biến đổi ; nó là một sự thay thế nhanh hơn - tất cả trong bộ nhớ cùng một lúc - thay thế cho ForEach-Object lệnh ghép ngắn dựa trên đường ống ( %) .


So sánh hiệu suất của các phương pháp khác nhau

Dưới đây là thời gian mẫu cho các cách tiếp cận khác nhau, dựa trên bộ sưu tập các 10,000đối tượng đầu vào , tính trung bình trên 10 lần chạy; các số tuyệt đối không quan trọng và thay đổi dựa trên nhiều yếu tố, nhưng nó sẽ mang lại cho bạn cảm giác về hiệu suất tương đối (thời gian đến từ máy ảo Windows 10 lõi đơn:

Quan trọng

  • Hiệu suất tương đối thay đổi dựa trên việc các đối tượng đầu vào là các thể hiện của các loại .NET thông thường (ví dụ: như đầu ra theo Get-ChildItem) hoặc các [pscustomobject]thể hiện (ví dụ như là đầu ra của Convert-FromCsv).
    Lý do là các [pscustomobject]thuộc tính được PowerShell quản lý động và nó có thể truy cập chúng nhanh hơn các thuộc tính thông thường của loại .NET thông thường (được xác định tĩnh). Cả hai kịch bản được đề cập dưới đây.

  • Các thử nghiệm sử dụng các bộ sưu tập đã có trong bộ nhớ đầy đủ làm đầu vào, để tập trung vào hiệu suất trích xuất thuộc tính thuần túy. Với lệnh gọi lệnh cmdlet / hàm làm đầu vào, sự khác biệt về hiệu suất thường sẽ ít rõ rệt hơn, vì thời gian bên trong cuộc gọi đó có thể chiếm phần lớn thời gian sử dụng.

  • Để đơn giản, bí danh %được sử dụng cho ForEach-Objectlệnh ghép ngắn.

Kết luận chung , áp dụng cho cả loại .NET thông thường và [pscustomobject]đầu vào:

  • Bảng liệt kê thành viên ( $collection.Name) và foreach ($obj in $collection)các giải pháp nhanh nhất , nhanh hơn 10 lần so với giải pháp dựa trên đường ống nhanh nhất.

  • Đáng ngạc nhiên, % Namethực hiện tồi tệ hơn nhiều % { $_.Name }- xem vấn đề GitHub này .

  • PowerShell Core luôn vượt trội so với Windows Powershell tại đây.

Thời gian với các loại .NET thông thường :

  • 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

Kết luận:

  • Trong PowerShell Core , .ForEach('Name')rõ ràng vượt trội hơn .ForEach({ $_.Name }). Trong Windows PowerShell, thật kỳ lạ, cái sau nhanh hơn, mặc dù chỉ là một chút.

Thời gian với các [pscustomobject]trường hợp :

  • 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

Kết luận:

  • Lưu ý làm thế nào với [pscustomobject]đầu vào .ForEach('Name')bằng cách vượt xa biến thể dựa trên khối tập lệnh , .ForEach({ $_.Name }).

  • Tương tự, [pscustomobject]đầu vào làm cho đường ống Select-Object -ExpandProperty Namenhanh hơn, trong Windows PowerShell gần như ngang bằng .ForEach({ $_.Name }), nhưng trong PowerShell Core vẫn chậm hơn khoảng 50%.

  • Tóm lại: Với ngoại lệ kỳ lạ % Name, với [pscustomobject]các phương thức tham chiếu dựa trên chuỗi các thuộc tính vượt trội hơn các thuộc tính dựa trên scriptblock.


Mã nguồn cho các bài kiểm tra :

Ghi chú:

  • Tải xuống chức năng Time-Commandtừ Gist này để chạy các thử nghiệm này.

  • Thay vào đó, thiết lập $useCustomObjectInputđể $trueđo bằng các [pscustomobject]trường hợp.

$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

Lưu ý, liệt kê thành viên chỉ hoạt động nếu bản thân bộ sưu tập không có thành viên cùng tên. Vì vậy, nếu bạn có một mảng các đối tượng FileInfo, bạn không thể có được một mảng có độ dài tệp bằng cách sử dụng

 $files.length # evaluates to array length

Và trước khi bạn nói "rõ ràng", hãy xem xét điều này. Nếu bạn có một mảng các đối tượng có thuộc tính dung lượng thì

 $objarr.capacity

công việc sẽ tốt TRỪ $ objarr đã thực sự không phải là một [Mảng] nhưng, ví dụ, một [ArrayList]. Vì vậy, trước khi sử dụng bảng liệt kê thành viên, bạn có thể phải nhìn vào bên trong hộp đen chứa bộ sưu tập của mình.

(Lưu ý với người điều hành: đây phải là một nhận xét về câu trả lời của rageandqq nhưng tôi chưa có đủ danh tiếng.)


Đó là một điểm tốt; yêu cầu tính năng GitHub này yêu cầu một cú pháp riêng cho phép liệt kê thành viên. Cách giải quyết cho các va chạm tên là sử dụng .ForEach()phương thức mảng như sau:$files.ForEach('Length')
mkuity0
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.