PowerShell에서 배열의 모든 객체에서 하나의 속성 값을 선택하십시오.


134

$ objects 객체의 배열이 있다고 가정 해 봅시다. 이러한 객체에 "Name"속성이 있다고 가정 해 봅시다.

이것이 내가하고 싶은 일입니다.

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

이것은 효과가 있지만 더 나은 방법으로 할 수 있습니까?

내가 같은 것을하면 :

 $results = objects | select Name

$resultsName 속성을 가진 객체의 배열입니다. $ results에 Names 배열이 포함되기를 원합니다.

더 좋은 방법이 있습니까?


4
완벽을 기하기 위해 foreach가 Name : 만 선택하도록 원래 코드에서 "+ ="를 제거 할 수도 있습니다 $results = @($objects | %{ $_.Name }). Scott 의 대답이 일반적으로 더 좋다고 생각하지만 명령 줄에 입력하는 것이 더 편리 할 수 ​​있습니다 .
황제 XLII

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 + 기능입니다.
    • 또는 PSv2 에서 foreach 명령문을 사용하면 출력을 변수에 직접 지정할 수도 있습니다.
      $ results = foreach ($ objects의 $ obj) {$ obj.Name}
    • 트레이드 오프 :
      • 둘 다 입력 수집 및 출력 배열 메모리 에 전체 적으로 맞아야합니다 .
      • 입력 콜렉션 자체가 명령 (파이프 라인)의 결과 인 경우 (예 :) (Get-ChildItem).Name, 해당 명령을 먼저 실행하여 완료해야합니다. 결과 배열의 요소에 액세스 해야합니다.
  • A의 파이프 라인 결과는 추가 처리되어야 곳에 나 결과는 전체로서 메모리에 맞지 않는 용도 :

    $ objects | Select-Object -ExpandProperty 이름

    • 그 필요성 -ExpandPropertyScott Saad의 답변에 설명되어 있습니다.
    • 일대일 처리의 일반적인 파이프 라인 이점을 얻을 수 있습니다. 이는 일반적으로 결과를 즉시 생성하고 메모리 사용을 일정하게 유지합니다 (결국 결과를 메모리에 수집하지 않는 한).
    • 거래 :
      • 파이프 라인의 사용 은 비교적 느립니다 .

들어 작은 입력 컬렉션 (배열), 당신은 아마 차이를 통지하지 않습니다 때로는 명령을 입력 할 수있는, 특히 명령 행에, 그리고 쉽게 더 중요하다.


여기이고 쉬운 타입 대안 그러나이며 느린 접근 방식 ; 연산 문 (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 cmdlet을 ( %) .


다양한 접근 방식의 성능 비교

다음은 10 개의 실행에 걸쳐 평균화 된 객체 컬렉션을 기반으로 한 다양한 접근 방식에 대한 샘플 타이밍 입니다 . 절대 숫자는 중요하지 않으며 많은 요소에 따라 다르지만 상대적인 감각을 가져야합니다.10,000 성능에 합니다 (타이밍은 단일 코어 Windows 10 VM에서 제공됨).

중대한

  • 상대적인 성능은 입력 오브젝트의 인스턴스인지에 따라 달라 일반적인 .NET 형식 (출력 등에 의해, 예를 들면 Get-ChildItemOR) [pscustomobject]인스턴스 (의한 출력으로서, 예를 들면 Convert-FromCsv).
    그 이유는 [pscustomobject]PowerShell에서 속성을 동적으로 관리하기 때문에 정적으로 정의 된 일반 .NET 유형의 일반 속성보다 빠르게 액세스 할 수 있기 때문입니다. 두 시나리오 모두 아래에 설명되어 있습니다.

  • 이 테스트에서는 전체 메모리 내 전체 컬렉션을 입력으로 사용하여 순수한 속성 추출 성능에 중점을 둡니다. 스트리밍 cmdlet / 함수 호출을 입력으로 사용하면 호출 내부에 소요 된 시간이 대부분의 시간을 차지할 수 있으므로 성능 차이는 일반적으로 훨씬 덜 두드러집니다.

  • 간결성 %을 위해 ForEach-Objectcmdlet 에는 별칭 이 사용됩니다 .

일반 .NET 유형과 [pscustomobject]입력 모두에 적용되는 일반 결론 :

  • 멤버 열거 형 ( $collection.Name)과 foreach ($obj in $collection)솔루션은 가장 빠른 파이프 라인 기반 솔루션보다 10 배 이상 빠릅니다.

  • 놀랍게도이 GitHub 문제를 보는 % Name것보다 훨씬 더 나쁜 성능을 % { $_.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 Core의 경우 여전히 약 50 % 느립니다.

  • 요컨대 : 홀수의 제외 % Name와, [pscustomobject]등록 정보를 참조하는 스트링 계 메소드 스크립트 블록 기반 것들 뛰어나다.


테스트 소스 코드 :

노트 :

  • 이 GistTime-Command 에서 기능 을 다운로드 하여 이러한 테스트를 실행하십시오.

  • 설정 $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

그리고 "잘"라고 말하기 전에 이것을 고려하십시오. capacity 속성이있는 객체 배열이있는 경우

 $objarr.capacity

겠습니까 잘 작동 되지 않은 경우로 예를 들어 실제로 있었다 objarr $ 아닌 [배열]하지만,이 [ArrayList를]. 따라서 멤버 열거 를 사용하기 전에 컬렉션이 포함 된 블랙 박스 내부를 살펴 봐야 할 수도 있습니다.

(중재자 참고 사항 : rageandqq의 답변에 대한 의견이어야하지만 아직 평판이 충분하지 않습니다.)


좋은 지적입니다. 이 GitHub 기능 요청 은 멤버 열거에 대한 별도의 구문을 요청 합니다. 이름 충돌에 대한 해결 .ForEach()방법은 다음과 같이 배열 방법 을 사용하는 것입니다.$files.ForEach('Length')
mklement0
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.