[arrays] PowerShell에서 배열의 모든 객체에서 하나의 속성 값을 선택하십시오.
$ objects 객체의 배열이 있다고 가정 해 봅시다. 이러한 객체에 “Name”속성이 있다고 가정 해 봅시다.
이것이 내가하고 싶은 일입니다.
$results = @()
$objects | %{ $results += $_.Name }
이것은 효과가 있지만 더 나은 방법으로 할 수 있습니까?
내가 같은 것을하면 :
$results = objects | select Name
$results
Name 속성을 가진 객체의 배열입니다. $ results에 Names 배열이 포함되기를 원합니다.
더 좋은 방법이 있습니까?
답변
의 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
답변
훨씬 쉬운 솔루션으로 다음을 사용할 수 있습니다.
$results = $objects.Name
$results
에있는 요소의 모든 ‘Name’속성 값의 배열로 채워 져야합니다 $objects
.
답변
기존의지도와 도움 답변 보완 할 수 있는 방법 사용하는 경우 와 성능 비교를 .
-
파이프 라인 외부 에서 (PSv3 +)를 사용하십시오.
$ objects . 이름
rageandqq의 대답 에서 알 수 있듯이 구문 상 단순하고 훨씬 빠릅니다 .
- 의 속성에 액세스 컬렉션 수준 해당 멤버의 값을 배열 로 가져 오는 것을 멤버 열거 라고 하며 PSv3 + 기능입니다.
- 또는 PSv2 에서
foreach
명령문을 사용하면 출력을 변수에 직접 지정할 수도 있습니다.$ results = foreach ($ objects의 $ obj) {$ obj.Name}
- 트레이드 오프 :
- 둘 다 입력 수집 및 출력 배열 메모리 에 전체 적으로 맞아야합니다 .
- 입력 콜렉션 자체가 명령 (파이프 라인)의 결과 인 경우 (예 🙂
(Get-ChildItem).Name
, 해당 명령을 먼저 실행하여 완료해야합니다. 결과 배열의 요소에 액세스 해야합니다.
-
A의 파이프 라인 결과는 추가 처리되어야 곳에 나 결과는 전체로서 메모리에 맞지 않는 용도 :
$ objects | Select-Object -ExpandProperty 이름
- 그 필요성
-ExpandProperty
은 Scott 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-ChildItem
OR)[pscustomobject]
인스턴스 (의한 출력으로서, 예를 들면Convert-FromCsv
).
그 이유는[pscustomobject]
PowerShell에서 속성을 동적으로 관리하기 때문에 정적으로 정의 된 일반 .NET 유형의 일반 속성보다 빠르게 액세스 할 수 있기 때문입니다. 두 시나리오 모두 아래에 설명되어 있습니다. -
이 테스트에서는 전체 메모리 내 전체 컬렉션을 입력으로 사용하여 순수한 속성 추출 성능에 중점을 둡니다. 스트리밍 cmdlet / 함수 호출을 입력으로 사용하면 호출 내부에 소요 된 시간이 대부분의 시간을 차지할 수 있으므로 성능 차이는 일반적으로 훨씬 덜 두드러집니다.
-
간결성
%
을 위해ForEach-Object
cmdlet 에는 별칭 이 사용됩니다 .
일반 .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]
등록 정보를 참조하는 스트링 계 메소드 스크립트 블록 기반 것들 뛰어나다.
테스트 소스 코드 :
노트 :
-
이 Gist
Time-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*
답변
주의, 멤버 열거 는 컬렉션 자체에 같은 이름의 멤버가없는 경우에만 작동합니다. 따라서 FileInfo 객체 배열이 있으면 다음을 사용하여 파일 길이 배열을 얻을 수 없습니다.
$files.length # evaluates to array length
그리고 “잘”라고 말하기 전에 이것을 고려하십시오. capacity 속성이있는 객체 배열이있는 경우
$objarr.capacity
겠습니까 잘 작동 되지 않은 경우로 예를 들어 실제로 있었다 objarr $ 아닌 [배열]하지만,이 [ArrayList를]. 따라서 멤버 열거 를 사용하기 전에 컬렉션이 포함 된 블랙 박스 내부를 살펴 봐야 할 수도 있습니다.
(중재자 참고 사항 : rageandqq의 답변에 대한 의견이어야하지만 아직 평판이 충분하지 않습니다.)