PowerShellで配列のすべてのオブジェクトの1つのプロパティの値を選択します


134

$ objectsというオブジェクトの配列があるとします。これらのオブジェクトに「名前」プロパティがあるとしましょう。

これが私がやりたいことです

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

これは機能しますが、より良い方法で実行できますか?

私が次のようなことをした場合:

 $results = objects | select Name

$resultsNameプロパティを持つオブジェクトの配列です。$ resultsに名前の配列を含めたいです。

もっと良い方法はありますか?


4
完全を期すために、元のコードから「+ =」を削除して、foreachがName:のみを選択するようにすることもできます$results = @($objects | %{ $_.Name })。これは、コマンドラインで入力する方が便利な場合がありますが、スコットの答えの方が一般的には良いと思います。
皇帝XLII、2011

1
@EmperorXLII:良い点、およびPSV3に+あなたもに簡素化することができます:$objects | % Name
mklement0

回答:


212

ExpandPropertyパラメータが使えるかもしれませんねSelect-Object

たとえば、現在のディレクトリのリストを取得し、Nameプロパティのみを表示するには、次のようにします。

ls | select -Property Name

これはまだDirectoryInfoまたはFileInfoオブジェクトを返します。Get-Member(エイリアスgm)にパイプすることで、パイプラインを通過する型をいつでも検査できます。

ls | select -Property Name | gm

したがって、オブジェクトを展開して、見ているプロパティのタイプのオブジェクトになるようにするには、次のようにします。

ls | select -ExpandProperty Name

あなたの場合、次のようにして変数を文字列の配列にすることができます。ここで、文字列はNameプロパティです。

$objects = ls | select -ExpandProperty Name

73

さらに簡単な解決策として、次のものを使用できます。

$results = $objects.Name

$resultsの要素のすべての「Name」プロパティ値の配列で埋める必要があります$objects


これはで機能しないことに注意してくださいExchange Management Shell。Exchangeを使用する場合、我々は、使用する必要があります$objects | select -Property Propname, OtherPropname
Bassie

2
@Bassie:コレクションレベルでプロパティにアクセスして配列をメンバーの値として取得することは、メンバー列挙と呼ばれ、PSv3 +の機能です。おそらく、Exchange管理シェルはPSv2です。
mklement0

32

既存の役立つ回答を補足するために、どのアプローチを使用するかについてのガイダンスとパフォーマンスの比較を示します。

  • パイプラインのでは、(PSv3 +)を使用します。

    $ objects 名前
    rageandqqの回答で示されているように、構文的に単純であり、はるかに高速です。

    • コレクションレベルでプロパティにアクセスして配列としてメンバーの値を取得することはメンバー列挙と呼ばれ、PSv3 +の機能です。
    • または、PSv2foreach ステートメントを使用します。その出力を変数に直接割り当てることもできます。
      $ results = foreach($ obj in $ objects){$ obj.Name}
    • トレードオフ
      • 入力コレクションと出力配列の 両方が全体としてメモリに収まる必要があります。
      • 入力コレクション自体がコマンド(パイプライン)(例:)の結果である場合、結果の配列の要素にアクセスするには、まず(Get-ChildItem).Nameそのコマンドを実行して完了する必要があります。
  • 結果をさらに処理する必要があるパイプライン、または結果が全体としてメモリに収まらないパイプラインでは、以下を使用します。

    $ objects | Select-Object -ExpandProperty名

    • 必要性について-ExpandPropertyは、Scott Saadの回答で説明されています
    • 1つずつ処理することで通常のパイプラインの利点が得られます。これにより、通常、すぐに出力が生成され、メモリの使用量が一定に保たれます(最終的にメモリに結果を収集しない限り)。
    • トレードオフ
      • パイプラインの使用は比較的遅いです。

以下のために小さな入力コレクション(配列)は、おそらく違いに気付くことはありません、そして、特にコマンドラインで、時には簡単にコマンドを入力できることがより重要です。


ここに入力するのは簡単ですが、これは最も遅いアプローチです。これは操作ステートメント(ここでも、PSv3 +)と呼ばれる簡略化されたForEach-Object構文を使用します。たとえば、次のPSv3 +ソリューションは、既存のコマンドに簡単に追加できます。

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

完全を期すために、あまり知られていないPSv4 + .ForEach() 配列方式この記事でより包括的に説明しています)は、さらに別の代替手段です。

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

# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
  • このアプローチは、パイプラインロジックが適用されないことを除いて、同じトレードオフでメンバー列挙似てます。パイプラインよりも著しく高速ですが、わずかに遅いです。

  • 名前文字列引数)によって単一のプロパティ値を抽出する場合、このソリューションはメンバー列挙と同等です(後者の方が構文的に単純です)。

  • スクリプトブロック変異体は、任意の可能な変換を。これは、パイプラインベースのForEach-Object コマンドレット%)に代わる、より高速なオールインメモリアットワンスの代替手段です。


さまざまなアプローチのパフォーマンスの比較

以下は、10回の実行にわたって平均された、オブジェクトの入力コレクションに基づく、さまざまなアプローチのサンプルタイミングです。絶対数は重要ではなく、多くの要因に基づいて変化しますが、相対的なパフォーマンスの感覚を与えるはずです(タイミングはシングルコアWindows 10 VMから取得されます:10,000

重要

  • 相対的なパフォーマンスは、入力オブジェクトが通常の.NETタイプのインスタンス(例:による出力Get-ChildItem)であるか[pscustomobject]インスタンス(例:による出力)であるかによって異なりますConvert-FromCsv
    その理由は、[pscustomobject]プロパティはPowerShellによって動的に管理され、(静的に定義された)通常の.NETタイプの通常のプロパティよりも速くプロパティにアクセスできるためです。以下では、両方のシナリオについて説明します。

  • テストでは、純粋なプロパティ抽出パフォーマンスに焦点を合わせるために、すでにメモリ内に完全なコレクションを入力として使用します。ストリーミングコマンドレット/関数呼び出しを入力として使用すると、その呼び出し内で費やされた時間が費やされた時間の大半を占める可能性があるため、パフォーマンスの違いは通常、それほど顕著ではなくなります。

  • 簡潔にするために、エイリアス%ForEach-Objectコマンドレットに使用されます。

一般的な結論。通常の.NETタイプと[pscustomobject]入力の両方に適用できます。

  • メンバーの列挙($collection.Name)とforeach ($obj in $collection)ソリューションは、パイプラインベースの最速のソリューションよりも10倍以上高速であり、はるかに高速です。

  • 驚いたことに、% NameパフォーマンスはGitHubの問題を% { $_.Name }ご覧ください。

  • PowerShell Coreは、一貫してWindows Powershellよりも優れています。

通常の.NETタイプのタイミング

  • 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

結論:

  • PowerShell Coreでは、.ForEach('Name')明らかにパフォーマンスが優れてい.ForEach({ $_.Name })ます。Windows PowerShellでは、奇妙なことに、後者の方がわずかですが、高速です。

[pscustomobject]インスタンスのタイミング

  • 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

結論:

  • [pscustomobject]入力.ForEach('Name')がスクリプトブロックベースのバリアントよりもはるかに優れていることに注意してください.ForEach({ $_.Name })

  • 同様に、[pscustomobject]入力により、Select-Object -ExpandProperty Name実質的にWindows PowerShellと同等のパイプラインベースの処理が高速になります.ForEach({ $_.Name })が、PowerShellコアでは依然として約50%遅くなります。

  • 要するに:の奇数除き% Nameと、[pscustomobject]プロパティを参照の文字列ベースの方法スクリプトブロックベースのものを上回ります。


テストのソースコード

注意:

  • これらのテストを実行するにはTime-CommandこのGistから関数をダウンロードしてください。

  • 設定$useCustomObjectInputする$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

注意は、メンバーの列挙は、コレクション自体は同じ名前のメンバーを持っていない場合にのみ機能します。したがって、FileInfoオブジェクトの配列がある場合、次を使用してファイル長の配列を取得することはできません。

 $files.length # evaluates to array length

「明らかに」と言う前に、これを考慮してください。容量プロパティを持つオブジェクトの配列がある場合

 $objarr.capacity

罰金をうまくいく場合を除き、実際に[アレイ]しかし、例えば、[ArrayListの]はありませんでしたobjarr $。そのため、メンバー列挙を使用する前に、コレクションを含むブラックボックス内を確認する必要がある場合があります。

(モデレーターへのメモ:これはrageandqqの回答へのコメントであるべきですが、まだ十分な評判がありません。)


それは良い点です。このGitHub機能のリクエストでは、メンバーを列挙するための個別の構文を要求します。名前の衝突を回避するには、使用することで.ForEach()次のように配列方法:$files.ForEach('Length')
mklement0
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.