다음을 통해 공유


해시 테이블에 대해 알고 싶었던 모든 것

한 걸음 뒤로 물러서서 해시 테이블에 대해 이야기하고 싶습니다. 나는 지금 항상 그들을 사용합니다. 어젯밤 사용자 그룹 모임이 끝난 후 누군가에 대해 가르치고 있었고, 나는 그가 가진 것과 똑같은 혼란을 가지고 있다는 것을 깨달았다. 해시 테이블은 PowerShell에서 매우 중요하므로 해시 테이블을 확실하게 이해하는 것이 좋습니다.

비고

이 문서의 원래 버전@KevinMarquette작성한 블로그에 나타났습니다. PowerShell 팀은 이 콘텐츠를 공유해 주신 Kevin에게 감사드립니다. PowerShellExplained.com자신의 블로그를 확인하세요.

사물의 컬렉션으로 해시 테이블

먼저 해시 테이블의 기존 정의에서 해시 테이블 컬렉션으로 표시하려고 합니다. 이 정의는 나중에 고급 콘텐츠에 익숙해질 때 작동 방식에 대한 기본적인 이해를 제공합니다. 이러한 이해를 건너뛰는 것은 종종 혼란의 원인입니다.

배열이란?

해시 테이블 에 대해 설명하기 전에, 먼저 배열을 언급해야 합니다. 이 논의의 목적을 위해 배열은 값 또는 개체의 목록 또는 컬렉션입니다.

$array = @(1,2,3,5,7,11)

항목을 배열에 넣으면 foreach 사용하여 목록을 반복하거나 인덱스로 배열의 개별 요소에 액세스할 수 있습니다.

foreach($item in $array)
{
    Write-Output $item
}

Write-Output $array[3]

인덱스도 같은 방식으로 업데이트할 수 있습니다.

$array[2] = 13

아직 배열의 기초만 배웠지만, 그렇기 때문에 해시 테이블을 공부할 때 더 잘 이해할 수 있을 것입니다.

해시 테이블이란?

PowerShell에서 사용하는 다른 방법으로 전환하기 전에 일반적으로 해시 테이블이 무엇인지에 대한 기본 기술 설명부터 시작하겠습니다.

해시 테이블은 키를 사용하여 각 값(개체)을 저장하는 것을 제외하고는 배열과 매우 유사한 데이터 구조입니다. 기본 키/값 저장소입니다. 먼저 빈 해시 테이블을 만듭니다.

$ageList = @{}

괄호 대신 중괄호는 해시 테이블을 정의하는 데 사용됩니다. 그런 다음 다음과 같은 키를 사용하여 항목을 추가합니다.

$key = 'Kevin'
$value = 36
$ageList.Add( $key, $value )

$ageList.Add( 'Alex', 9 )

내가 저장하고 싶은 값은 사람의 나이이며, 이름은 키입니다.

액세스에 대괄호 사용

해시 파일에 값을 추가한 후에는 배열에 사용할 수 있는 것처럼 숫자 인덱스 대신 동일한 키를 사용하여 값을 다시 끌어올 수 있습니다.

$ageList['Kevin']
$ageList['Alex']

케빈의 나이를 원할 때, 나는 그의 이름을 사용하여 액세스합니다. 이 방법을 사용하여 해시 테이블에도 값을 추가하거나 업데이트할 수 있습니다. 위의 Add() 메서드를 사용하는 것과 같습니다.

$ageList = @{}

$key = 'Kevin'
$value = 36
$ageList[$key] = $value

$ageList['Alex'] = 9

이후 섹션에서 다룰 값에 액세스하고 업데이트하는 데 사용할 수 있는 또 다른 구문이 있습니다. 다른 언어로 PowerShell을 사용하는 경우 이러한 예제는 이전에 해시 테이블을 사용한 방법과 일치해야 합니다.

값을 사용하여 해시 테이블 만들기

지금까지 이러한 예제에 대해 빈 해시 테이블을 만들었습니다. 키와 값을 생성할 때 미리 채워넣을 수 있습니다.

$ageList = @{
    Kevin = 36
    Alex  = 9
}

조회 테이블로서

이러한 유형의 해시 테이블의 실제 값은 조회 테이블로 사용할 수 있다는 것입니다. 다음은 간단한 예입니다.

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

$server = $environments[$env]

이 예제에서는 $env 변수에 대한 환경을 지정하고 올바른 서버를 선택합니다. 이와 같은 선택에 switch($env){...} 사용할 수 있지만 해시 테이블은 좋은 옵션입니다.

나중에 사용할 조회 테이블을 동적으로 빌드할 때 더 좋아집니다. 따라서 참조 항목을 상호 참조해야 하는 경우 이 방법을 사용하는 것이 좋습니다. PowerShell이 Where-Object을 사용하여 파이프에서 필터링하는 데 매우 능숙하지 않았다면, 우리는 이것을 더 많이 볼 수 있었을 것입니다. 성능이 중요한 경우 이 접근 방식을 고려해야 합니다.

더 빠르다고 말하지는 않겠지만 성능이 중요한 경우 테스트합니다.

다중 선택

일반적으로 해시 테이블을 하나의 키를 제공하고 하나의 값을 가져오는 키/값 쌍으로 간주합니다. PowerShell을 사용하면 키 배열을 제공하여 여러 값을 가져올 수 있습니다.

$environments[@('QA','DEV')]
$environments[('QA','DEV')]
$environments['QA','DEV']

이 예제에서는 위의 동일한 조회 해시 테이블을 사용하고 세 가지 배열 스타일을 제공하여 일치 항목을 가져옵니다. 대부분의 사람들이 인식하지 못하는 PowerShell의 숨겨진 보석입니다.

해시 테이블을 반복 처리하기

해시 테이블은 키/값 쌍의 컬렉션이므로 배열 또는 일반 항목 목록과 다르게 반복합니다.

가장 먼저 주의해야 할 점은 해시 테이블을 파이프하면 파이프가 이를 하나의 개체처럼 처리한다는 것입니다.

PS> $ageList | Measure-Object
count : 1

Count 속성이 포함된 값의 수를 알려주긴 하지만.

PS> $ageList.Count
2

값만 필요한 경우 Values 속성을 사용하여 이 문제를 해결할 수 있습니다.

PS> $ageList.Values | Measure-Object -Average
Count   : 2
Average : 22.5

키를 열거하고 값에 액세스하는 데 사용하는 것이 더 유용한 경우가 많습니다.

PS> $ageList.Keys | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_, $ageList[$_]
    Write-Output $message
}
Kevin is 36 years old
Alex is 9 years old

다음은 foreach(){...} 루프를 사용하는 동일한 예제입니다.

foreach($key in $ageList.Keys)
{
    $message = '{0} is {1} years old' -f $key, $ageList[$key]
    Write-Output $message
}

해시 테이블의 각 키를 순회하며 이를 사용하여 값에 액세스합니다. 이는 hashtables를 컬렉션으로 사용할 때 일반적인 패턴입니다.

GetEnumerator()

이제 해시 테이블을 순회하기 위해 GetEnumerator()으로 넘어갈 차례입니다.

$ageList.GetEnumerator() | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_.Key, $_.Value
    Write-Output $message
}

열거자는 각 키/값 쌍을 하나씩 제공합니다. 이 사용 사례를 위해 특별히 설계되었습니다. 마크 크라우스 님이 이것을 상기시켜 주셔서 감사합니다.

잘못된 열거

한 가지 중요한 세부 정보는 해시 테이블이 열거되는 동안에는 수정할 수 없다는 것입니다. 기본 $environments 예제로 시작하는 경우:

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

그리고 모든 키를 동일한 서버 값으로 설정하려고 하면 실패합니다.

$environments.Keys | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

An error occurred while enumerating through a collection: Collection was modified;
enumeration operation may not execute.
+ CategoryInfo          : InvalidOperation: tableEnumerator:HashtableEnumerator) [],
 RuntimeException
+ FullyQualifiedErrorId : BadEnumeration

그것도 잘 될 것 같아 보여도 실패할 것입니다.

foreach($key in $environments.Keys) {
    $environments[$key] = 'SrvDev03'
}

Collection was modified; enumeration operation may not execute.
    + CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException

이 상황의 비결은 열거를 수행하기 전에 키를 복제하는 것입니다.

$environments.Keys.Clone() | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

속성의 모음으로서의 해시 테이블

지금까지 해시 테이블의 개체 유형은 모두 동일한 형식의 개체였습니다. 나는 그 모든 예에서 나이를 사용했고 열쇠는 사람의 이름이었다. 개체 컬렉션에 각각 이름이 있을 때 이를 확인할 수 있는 좋은 방법입니다. PowerShell에서 해시 테이블을 사용하는 또 다른 일반적인 방법은 키가 속성 이름인 속성의 모임을 보유하는 것입니다. 이 다음 예제에서는 해당 아이디어를 한 단계씩 살펴보겠습니다.

속성 기반 액세스

속성 기반 액세스를 사용하면 해시 테이블의 역학과 PowerShell에서 이를 사용하는 방법이 변경됩니다. 다음은 키를 속성으로 처리하는 위의 일반적인 예입니다.

$ageList = @{}
$ageList.Kevin = 35
$ageList.Alex = 9

위의 예제와 마찬가지로 이 예제에서는 해시 테이블이 아직 없는 경우 해당 키를 추가합니다. 키를 정의하는 방법과 값에 따라 약간 이상하거나 완벽하게 맞습니다. 연령 목록 예제는 이 시점까지 잘 작동했습니다. 이를 위해 앞으로 나아갈 수 있는 새로운 예가 필요합니다.

$person = @{
    name = 'Kevin'
    age  = 36
}

또한 다음과 같이 $person 특성을 추가하고 액세스할 수 있습니다.

$person.city = 'Austin'
$person.state = 'TX'

갑자기 이 해시 테이블이 객체처럼 느껴지고 동작하기 시작합니다. 여전히 사물의 컬렉션이므로 위의 모든 예제가 계속 적용됩니다. 우리는 단지 다른 관점에서 접근합니다.

키 및 값 확인

대부분의 경우 다음과 같이 값을 테스트할 수 있습니다.

if( $person.age ){...}

그것은 간단하지만 내 논리에서 하나의 중요한 세부 사항을 간과했기 때문에 나를 위해 많은 버그의 소스되었습니다. 키가 있는지 확인하기 위해 사용하기 시작했습니다. 값이 $false 또는 0이면 해당 문이 예기치 않게 $false 반환합니다.

if( $person.age -ne $null ){...}

이 해결책은 값이 0일 때의 문제를 해결하지만, $null과 존재하지 않는 키의 문제는 해결하지 못합니다. 대부분의 경우 이러한 차이점을 구분할 필요는 없지만, 그렇게 할 때를 위한 방법이 있습니다.

if( $person.ContainsKey('age') ){...}

또한 키를 모르거나 전체 컬렉션을 반복하지 않고 값을 테스트해야 하는 상황에 대한 ContainsValue() 있습니다.

키 제거 및 지우기

Remove() 메서드를 사용하여 키를 제거할 수 있습니다.

$person.Remove('age')

$null 값을 할당하면 결과적으로 $null 값이 있는 키가 남게 됩니다.

해시 테이블을 지우는 일반적인 방법은 빈 해시 테이블로 초기화하는 것입니다.

$person = @{}

그것이 작동하기는 하지만, 대신 Clear() 메서드를 사용하길 권장합니다.

$person.Clear()

이 메서드를 사용하면 자체 문서화 코드가 만들어지고 코드의 의도를 매우 깔끔하게 만드는 인스턴스 중 하나입니다.

모든 재미있는 물건

순서가 지정된 해시 테이블

기본적으로 해시 테이블은 정렬되지 않습니다. 기존 컨텍스트에서 항상 키를 사용하여 값에 액세스하는 경우 순서는 중요하지 않습니다. 속성이 정의한 순서대로 유지되도록 할 수 있습니다. 고맙게도 ordered 키워드를 사용하여 수행할 수 있는 방법이 있습니다.

$person = [ordered]@{
    name = 'Kevin'
    age  = 36
}

이제 키와 값을 열거할 때 해당 순서대로 유지됩니다.

인라인 해시 테이블

한 줄에서 해시 테이블을 정의하는 경우 키/값 쌍을 세미콜론으로 구분할 수 있습니다.

$person = @{ name = 'kevin'; age = 36; }

파이프에서 만드는 경우 이 기능이 편리합니다.

공통 파이프라인 명령의 사용자 지정 식

해시 테이블을 사용하여 사용자 지정 또는 계산 속성을 만드는 데 지원하는 몇 가지 cmdlet이 있습니다. 일반적으로 Select-ObjectFormat-Table과 함께 볼 수 있습니다. 해시 테이블은 완전히 확장될 때 다음과 같은 특수 구문을 갖습니다.

$property = @{
    Name = 'TotalSpaceGB'
    Expression = { ($_.Used + $_.Free) / 1GB }
}

cmdlet은 그 열에 Name이라는 레이블을 지정합니다. Expression $_ 파이프에 있는 개체의 값인 경우 실행되는 스크립트 블록입니다. 작동 중인 스크립트는 다음과 같습니다.

$drives = Get-PSDrive | where Used
$drives | Select-Object -Property Name, $property

Name     TotalSpaceGB
----     ------------
C    238.472652435303

나는 그것을 변수에 배치했지만, 인라인으로 쉽게 정의할 수 있으며, 그동안 Namen로, Expressione로 단축할 수 있습니다.

$drives | Select-Object -Property Name, @{n='TotalSpaceGB';e={($_.Used + $_.Free) / 1GB}}

나는 개인적으로 명령어가 길어지는 것을 좋아하지 않으며, 그로 인해 발생할 수 있는 몇 가지 나쁜 행동에 대해선 깊이 들어가고 싶지 않습니다. 스크립트에서 이 방법을 사용하는 대신 원하는 모든 필드와 속성을 사용하여 새 해시 테이블 또는 pscustomobject 만들 가능성이 높습니다. 하지만 이 작업을 수행하는 코드가 많이 있으므로 알고 싶었습니다. 나중에 pscustomobject 만드는 것에 대해 이야기합니다.

사용자 지정 정렬 식

개체에 정렬하려는 데이터가 있는 경우 컬렉션을 쉽게 정렬할 수 있습니다. 데이터를 정렬하기 전에 개체에 추가하거나 Sort-Object대한 사용자 지정 식을 만들 수 있습니다.

Get-ADUser | Sort-Object -Property @{ e={ Get-TotalSales $_.Name } }

이 예제에서는 사용자 목록을 작성하고 일부 사용자 지정 cmdlet을 사용하여 정렬에 대한 추가 정보를 가져옵니다.

해시 테이블의 목록을 정렬

정렬하려는 해시 테이블 목록이 있다면, Sort-Object가 키를 속성으로 처리하지 않는다는 것을 알게 될 것입니다. 사용자 지정 정렬 식을 사용하여 이를 회피할 수 있습니다.

$data = @(
    @{name='a'}
    @{name='c'}
    @{name='e'}
    @{name='f'}
    @{name='d'}
    @{name='b'}
)

$data | Sort-Object -Property @{e={$_.name}}

cmdlet에서 해시 테이블 스플래팅

많은 사람들이 초기에 발견하지 않는 해시 테이블의 내가 좋아하는 점 중 하나입니다. 이 아이디어는 한 줄의 cmdlet에 모든 속성을 제공하는 대신 먼저 해시 테이블로 압축할 수 있다는 것입니다. 그런 다음 특별한 방법으로 함수에 해시 테이블을 제공할 수 있습니다. 다음은 일반적인 방식으로 DHCP 범위를 만드는 예제입니다.

Add-DhcpServerV4Scope -Name 'TestNetwork' -StartRange '10.0.0.2' -EndRange '10.0.0.254' -SubnetMask '255.255.255.0' -Description 'Network for testlab A' -LeaseDuration (New-TimeSpan -Days 8) -Type "Both"

스플래팅사용하지 않으면 모든 항목을 한 줄로 정의해야 합니다. 화면에서 벗어나 스크롤되거나 임의의 지점에서 줄이 바뀝니다. 이제 그것을 스플래팅을 사용하는 명령과 비교합니다.

$DHCPScope = @{
    Name          = 'TestNetwork'
    StartRange    = '10.0.0.2'
    EndRange      = '10.0.0.254'
    SubnetMask    = '255.255.255.0'
    Description   = 'Network for testlab A'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type          = "Both"
}
Add-DhcpServerV4Scope @DHCPScope

$ 대신에 @ 기호를 사용하면 스플랫 작업이 실행됩니다.

잠시 시간을 내어 그 예제를 읽는 것이 얼마나 쉬운지 알아봐 주세요. 모두 동일한 값을 가진 동일한 명령입니다. 두 번째는 앞으로 더 쉽게 이해하고 유지 관리할 수 있습니다.

명령이 너무 길어질 때마다 스플래팅을 사용합니다. 창이 오른쪽으로 스크롤되게 만드는 경우를 너무 길다고 정의합니다. 함수에 대한 세 가지 속성을 충족하게 되면 스플래트된 해시 테이블을 사용하여 다시 작성하게 될 가능성이 큽니다.

선택적 매개 변수에 대한 스플래팅

스플래팅을 사용하는 가장 일반적인 방법 중 하나는 스크립트의 다른 위치에서 제공되는 선택적 매개 변수를 처리하는 것입니다. 선택적 $Credential 인수가 있는 Get-CimInstance 호출을 래핑하는 함수가 있다고 가정해 보겠습니다.

$CIMParams = @{
    ClassName = 'Win32_BIOS'
    ComputerName = $ComputerName
}

if($Credential)
{
    $CIMParams.Credential = $Credential
}

Get-CimInstance @CIMParams

먼저 공통 매개 변수를 사용하여 해시 테이블을 만듭니다. 그런 다음 $Credential 추가합니다(있는 경우). 여기서 스플래팅을 사용하고 있으므로 코드에서 한 번만 Get-CimInstance 호출하면됩니다. 이 디자인 패턴은 매우 깨끗하며 많은 선택적 매개 변수를 쉽게 처리할 수 있습니다.

공평하게 매개 변수에 대한 $null 값을 허용하는 명령을 작성할 수 있습니다. 호출하는 다른 명령을 항상 제어할 수 있는 것은 아닙니다.

여러 스플래트

여러 해시 테이블을 동일한 cmdlet에 스플래트할 수 있습니다. 원래 스플래팅 예제를 다시 살펴보면:

$Common = @{
    SubnetMask  = '255.255.255.0'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type = "Both"
}

$DHCPScope = @{
    Name        = 'TestNetwork'
    StartRange  = '10.0.0.2'
    EndRange    = '10.0.0.254'
    Description = 'Network for testlab A'
}

Add-DhcpServerv4Scope @DHCPScope @Common

많은 명령에 전달하는 일반적인 매개 변수 집합이 있는 경우 이 메서드를 사용합니다.

깨끗한 코드를 위한 스플래팅

코드를 더 깔끔하게 만드는 경우 단일 매개 변수를 스플래팅하는 데 아무런 문제가 없습니다.

$log = @{Path = '.\logfile.log'}
Add-Content "logging this command" @log

실행 파일 스플래팅

스플래팅은 /param:value 구문을 사용하는 일부 실행 파일에서도 작동합니다. 예를 들어 Robocopy.exe다음과 같은 매개 변수가 있습니다.

$robo = @{R=1;W=1;MT=8}
robocopy source destination @robo

나는 이것이 모두 유용하다는 것을 알지 못하지만 흥미롭다는 것을 알게되었습니다.

해시 테이블 추가

Hashtables는 두 해시 테이블을 결합하는 추가 연산자를 지원합니다.

$person += @{Zip = '78701'}

이는 두 해시 테이블이 키를 공유하지 않는 경우에만 작동합니다.

중첩된 해시 테이블

해시 테이블 내의 값으로 해시 테이블을 사용할 수 있습니다.

$person = @{
    name = 'Kevin'
    age  = 36
}
$person.location = @{}
$person.location.city = 'Austin'
$person.location.state = 'TX'

두 개의 키를 포함하는 기본 해시 테이블로 시작했습니다. 빈 해시 테이블이 있는 location 키를 추가했습니다. 그런 다음 해당 location 해시 테이블의 마지막 두 항목을 추가했습니다. 우리는 이 모든 것을 인라인으로도 할 수 있습니다.

$person = @{
    name = 'Kevin'
    age  = 36
    location = @{
        city  = 'Austin'
        state = 'TX'
    }
}

이렇게 하면 위에서 본 것과 동일한 해시 테이블이 만들어지고 동일한 방식으로 속성에 액세스할 수 있습니다.

$person.location.city
Austin

객체의 구조를 설계하는 방법에는 여러 가지가 있습니다. 다음은 중첩된 해시 테이블을 보는 두 번째 방법입니다.

$people = @{
    Kevin = @{
        age  = 36
        city = 'Austin'
    }
    Alex = @{
        age  = 9
        city = 'Austin'
    }
}

이것은 해시테이블을 객체의 컬렉션과 속성의 컬렉션으로 사용하는 개념을 혼합합니다. 원하는 방법을 사용하여 중첩된 경우에도 값에 쉽게 액세스할 수 있습니다.

PS> $people.kevin.age
36
PS> $people.kevin['city']
Austin
PS> $people['Alex'].age
9
PS> $people['Alex']['City']
Austin

나는 속성처럼 취급 할 때 점 속성을 사용하는 경향이있다. 일반적으로 코드에서 정적으로 정의한 항목들이며 바로 기억할 수 있습니다. 목록을 탐색하거나 프로그래밍 방식으로 키에 액세스해야 하는 경우 대괄호를 사용하여 키 이름을 제공합니다.

foreach($name in $people.Keys)
{
    $person = $people[$name]
    '{0}, age {1}, is in {2}' -f $name, $person.age, $person.city
}

해시 테이블을 중첩할 수 있는 기능은 많은 유연성과 다양한 선택을 제공합니다.

중첩된 해시 테이블 살펴보기

해시 테이블 중첩을 시작하는 즉시 콘솔에서 쉽게 확인할 수 있는 방법이 필요합니다. 마지막 해시 테이블을 사용하면 다음과 같은 출력이 생성되며, 이는 제한된 깊이까지만 표시됩니다.

PS> $people
Name                           Value
----                           -----
Kevin                          {age, city}
Alex                           {age, city}

이러한 것들을 볼 때 내가 항상 사용하는 명령어는 ConvertTo-Json입니다. 이는 매우 깔끔하며, 다른 것들에 JSON을 자주 사용하기 때문입니다.

PS> $people | ConvertTo-Json
{
    "Kevin":  {
                "age":  36,
                "city":  "Austin"
            },
    "Alex":  {
                "age":  9,
                "city":  "Austin"
            }
}

JSON을 모르는 경우에도 원하는 내용을 볼 수 있어야 합니다. 이와 같은 구조화된 데이터에 대한 Format-Custom 명령이 있지만 여전히 JSON 보기를 더 좋아합니다.

개체 만들기

경우에 따라 개체가 있어야 하고 해시 테이블을 사용하여 속성을 보유하는 것만으로는 작업이 완료되지 않는 경우가 있습니다. 보통 키를 열 이름으로 표시하려고 합니다. pscustomobject이 그것을 쉽게 만듭니다.

$person = [pscustomobject]@{
    name = 'Kevin'
    age  = 36
}

$person

name  age
----  ---
Kevin  36

처음에는 pscustomobject 만들지 않더라도 필요할 때 언제든지 캐스팅할 수 있습니다.

$person = @{
    name = 'Kevin'
    age  = 36
}

[pscustomobject]$person

name  age
----  ---
Kevin  36

이미 pscustomobject에 대한 상세히 정리된 글이 있으니, 이 글을 읽고 나서 꼭 읽어보세요. 그것은 여기에서 배운 많은 것들을 기반으로합니다.

파일에 해시 테이블 읽기 및 쓰기

CSV에 저장

CSV에 저장하기 위해 해시 테이블을 얻는 데 어려움을 겪는 것은 위에서 언급한 어려움 중 하나입니다. 해시 테이블을 pscustomobject 변환하면 CSV에 올바르게 저장됩니다. 열 순서가 유지되도록 pscustomobject 시작하는 경우 도움이 됩니다. 필요한 경우 pscustomobject 인라인으로 변환할 수 있습니다.

$person | ForEach-Object{ [pscustomobject]$_ } | Export-Csv -Path $path

다시 말하지만, pscustomobject를 사용하여 내 작성물을 확인해보세요.

파일에 중첩된 해시 테이블 저장

중첩된 해시 테이블을 파일에 저장한 다음 다시 읽어야 하는 경우 JSON cmdlet을 사용하여 이 작업을 수행합니다.

$people | ConvertTo-Json | Set-Content -Path $path
$people = Get-Content -Path $path -Raw | ConvertFrom-Json

이 메서드에는 두 가지 중요한 점이 있습니다. 먼저 JSON이 여러 줄로 작성되므로 -Raw 옵션을 사용하여 단일 문자열로 다시 읽어야 합니다. 두 번째는 가져온 개체가 더 이상 [hashtable]없다는 것입니다. 지금은 [pscustomobject]이(가) 되었고, 예상하지 못하면 문제가 발생할 수 있습니다.

깊이 중첩된 해시 테이블에 주의하세요. JSON으로 변환할 때 예상한 결과를 얻지 못할 수 있습니다.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json

{
  "a": {
    "b": {
      "c": "System.Collections.Hashtable"
    }
  }
}

Depth 매개 변수를 사용하여 중첩된 해시 테이블을 모두 확장했는지 확인합니다.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json -Depth 3

{
  "a": {
    "b": {
      "c": {
        "d": "e"
      }
    }
  }
}

가져올 때 [hashtable] 필요한 경우 Export-CliXmlImport-CliXml 명령을 사용해야 합니다.

JSON을 Hashtable로 변환

JSON을 [hashtable]로 변환해야 할 경우, .NET의 JavaScriptSerializer를 사용하는 방법이 있습니다.

[Reflection.Assembly]::LoadWithPartialName("System.Web.Script.Serialization")
$JSSerializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new()
$JSSerializer.Deserialize($json,'Hashtable')

PowerShell v6부터 JSON 지원은 NewtonSoft JSON.NET 사용하고 해시 테이블 지원을 추가합니다.

'{ "a": "b" }' | ConvertFrom-Json -AsHashtable

Name      Value
----      -----
a         b

PowerShell 6.2는 Depth 매개 변수를 ConvertFrom-Json에 추가했습니다. 기본 깊이 1024입니다.

파일에서 직접 읽기

PowerShell 구문을 사용하여 해시 테이블을 포함하는 파일이 있는 경우 직접 가져오는 방법이 있습니다.

$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create( $content )
$scriptBlock.CheckRestrictedLanguage( $allowedCommands, $allowedVariables, $true )
$hashtable = ( & $scriptBlock )

파일 내용을 scriptblock에 가져온 후, 다른 PowerShell 명령이 포함되어 있지 않은지 확인한 다음 실행합니다.

이 메모에서 모듈 매니페스트(.psd1 파일)가 해시 테이블일 뿐이라는 것을 알고 계셨나요?

키는 모든 개체일 수 있습니다.

대부분의 경우 키는 문자열일 뿐입니다. 그래서 우리는 무엇이든 주위에 따옴표를 넣어 키로 만들 수 있습니다.

$person = @{
    'full name' = 'Kevin Marquette'
    '#' = 3978
}
$person['full name']

당신은 당신이 할 수 있다는 것을 깨닫지 못했을 수도 있는 몇 가지 이상한 일을 할 수 있습니다.

$person.'full name'

$key = 'full name'
$person.$key

당신이 뭔가를 할 수 있다고 해서, 그것은 당신이해야한다는 것을 의미하지 않는다. 마지막 버그가 발생하기를 기다리는 것처럼 보이며 코드를 읽는 사람이 쉽게 오해할 수 있습니다.

기술적으로 키는 문자열일 필요는 없지만 문자열만 사용하는 경우 쉽게 생각할 수 있습니다. 그러나 인덱싱은 복잡한 키와 잘 작동하지 않습니다.

$ht = @{ @(1,2,3) = "a" }
$ht

Name                           Value
----                           -----
{1, 2, 3}                      a

해당 키로 해시 테이블의 값에 액세스하는 것이 항상 작동하지는 않습니다. 다음은 그 예입니다.

$key = $ht.Keys[0]
$ht.$($key)
a
$ht[$key]
a

키가 배열인 경우 멤버 액세스(.) 표기법과 함께 사용할 수 있도록 하위 식에서 $key 변수를 래핑해야 합니다. 또는 배열 인덱스([]) 표기법을 사용할 수 있습니다.

자동 변수에 사용

$PSBoundParameters

$PSBoundParameters 함수의 컨텍스트 내에만 존재하는 자동 변수입니다. 함수가 호출된 모든 매개 변수를 포함합니다. 이것은 정확히 해시 테이블은 아니지만, 해시 테이블처럼 처리할 수 있을 만큼 가깝습니다.

여기에는 키를 제거하고 다른 함수에 스플래팅하는 것이 포함됩니다. 프록시 함수를 직접 작성하는 경우 이 함수를 자세히 살펴보세요.

자세한 내용은 about_Automatic_Variables를 참조하세요.

PS 바운드 파라미터 주의 사항

기억해야 할 중요한 한 가지는 매개 변수로 전달되는 값만 포함한다는 것입니다. 기본값이 있는 매개 변수도 있지만 호출자가 전달하지 않는 경우 $PSBoundParameters 해당 값이 포함되지 않습니다. 이것은 일반적으로 간과됩니다.

$PSDefaultParameterValues

이 자동 변수를 사용하면 cmdlet을 변경하지 않고 모든 cmdlet에 기본값을 할당할 수 있습니다. 이 예제를 살펴보세요.

$PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"

$PSDefaultParameterValues 해시 테이블에 항목을 추가하여 Out-File -Encoding 매개 변수의 기본값으로 UTF8을 설정합니다. 세션별이므로 사용자 $PROFILE에 배치해야 합니다.

이 값을 자주 사용하여 자주 입력하는 값을 미리 할당합니다.

$PSDefaultParameterValues[ "Connect-VIServer:Server" ] = 'VCENTER01.contoso.local'

또한 와일드카드도 허용하므로 값을 대량으로 설정할 수 있습니다. 이를 사용할 수 있는 몇 가지 방법은 다음과 같습니다.

$PSDefaultParameterValues[ "Get-*:Verbose" ] = $true
$PSDefaultParameterValues[ "*:Credential" ] = Get-Credential

자세한 내용은 Michael Sorens가 쓴 자동 기본값 에 관한 이 훌륭한 문서를 참조하세요.

Regex $Matches

-match 연산자를 사용하면 일치 결과를 사용하여 $Matches라는 자동 변수가 만들어집니다. regex에 하위 식이 있는 경우 해당 하위 일치 항목도 나열됩니다.

$message = 'My SSN is 123-45-6789.'

$message -match 'My SSN is (.+)\.'
$Matches[0]
$Matches[1]

지정된 일치 항목

이것은 대부분의 사람들이 모르는 내가 좋아하는 기능 중 하나입니다. 명명된 정규식 일치를 사용하는 경우 일치 항목의 이름으로 해당 일치 항목에 액세스할 수 있습니다.

$message = 'My Name is Kevin and my SSN is 123-45-6789.'

if($message -match 'My Name is (?<Name>.+) and my SSN is (?<SSN>.+)\.')
{
    $Matches.Name
    $Matches.SSN
}

위의 예제에서 (?<Name>.*) 명명된 하위 식입니다. 이 값은 $Matches.Name 속성에 배치됩니다.

Group-Object -AsHashtable

거의 알려지지 않은 Group-Object 기능 중 하나는 일부 데이터 세트를 해시 테이블로 변환할 수 있다는 것입니다.

Import-Csv $Path | Group-Object -AsHashtable -Property Email

이렇게 하면 각 행이 해시 테이블로 추가되고 지정된 속성을 키로 사용하여 액세스합니다.

해시 테이블 복사

한 가지 중요한 점은 해시 테이블이 객체라는 것입니다. 또한 각 변수는 개체에 대한 참조일 뿐입니다. 즉, 해시 테이블의 유효한 복사본을 만드는 데 더 많은 작업이 필요합니다.

참조 형식 할당

해시 테이블이 하나 있고 두 번째 변수에 할당하는 경우 두 변수 모두 동일한 해시 테이블을 가리킵니다.

PS> $orig = @{name='orig'}
PS> $copy = $orig
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [copy]

이는 한 값의 값을 변경하면 다른 값도 변경되기 때문에 동일하다는 것을 강조 표시합니다. 이는 해시 테이블을 다른 함수에 전달할 때도 적용됩니다. 해당 함수가 해당 해시 테이블을 변경하는 경우 원본도 변경됩니다.

단순 복사본, 단일 수준

위의 예제와 같이 간단한 해시 테이블이 있는 경우 Clone() 사용하여 단순 복사본을 만들 수 있습니다.

PS> $orig = @{name='orig'}
PS> $copy = $orig.Clone()
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [orig]

이렇게 하면 다른 항목에 영향을 주지 않는 기본 변경 내용을 적용할 수 있습니다.

중첩된 단순 복사본

단순 복사본이라고 하는 이유는 기본 수준 속성만 복사하기 때문입니다. 이러한 속성 중 하나가 참조 형식(예: 다른 해시 테이블)인 경우 중첩된 개체는 여전히 서로를 가리킵니다.

PS> $orig = @{
        person=@{
            name='orig'
        }
    }
PS> $copy = $orig.Clone()
PS> $copy.person.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.person.name
PS> 'Orig: [{0}]' -f $orig.person.name

Copy: [copy]
Orig: [copy]

따라서 해시 테이블을 복제했지만 person 대한 참조가 복제되지 않았음을 알 수 있습니다. 첫 번째 해시 테이블과 연결되지 않은 두 번째 해시 테이블을 실제로 만들려면 심층 복사본을 만들어야 합니다.

깊은 복사

해시 테이블의 전체 복사본을 만들고 해시 테이블로 유지하는 몇 가지 방법이 있습니다. 다음은 PowerShell을 사용하여 딥 카피를 재귀적으로 만드는 함수입니다.

function Get-DeepClone
{
    [CmdletBinding()]
    param(
        $InputObject
    )
    process
    {
        if($InputObject -is [hashtable]) {
            $clone = @{}
            foreach($key in $InputObject.Keys)
            {
                $clone[$key] = Get-DeepClone $InputObject[$key]
            }
            return $clone
        } else {
            return $InputObject
        }
    }
}

다른 참조 형식이나 배열을 처리하지는 않지만 좋은 시작점입니다.

또 다른 방법은 .NET을 사용하여 이 함수와 같이 CliXml 사용하여 역직렬화하는 것입니다.

function Get-DeepClone
{
    param(
        $InputObject
    )
    $TempCliXmlString = [System.Management.Automation.PSSerializer]::Serialize($obj, [int32]::MaxValue)
    return [System.Management.Automation.PSSerializer]::Deserialize($TempCliXmlString)
}

매우 큰 해시 테이블의 경우 크기가 확장되면 역직렬화 함수가 더 빠릅니다. 그러나 이 메서드를 사용할 때 고려해야 할 몇 가지 사항이 있습니다. CliXml 사용하므로 메모리가 많이 사용되며 거대한 해시 테이블을 복제하는 경우 문제가 될 수 있습니다. CliXml 또 다른 제한 사항은 깊이 제한이 48입니다. 즉, 중첩된 해시 테이블 계층이 48개인 해시 테이블이 있는 경우 복제가 실패하고 해시 테이블이 전혀 출력되지 않습니다.

다른 것?

나는 많은 진전을 빠르게 이루었다. 내 바람은 당신이 이것을 읽을 때마다 새로운 것을 배우거나 더 잘 이해하는 것입니다. 이 기능의 전체 스펙트럼을 다루었기 때문에 지금 당장은 적용되지 않을 수 있는 측면이 있습니다. 이는 완벽하게 정상이며 PowerShell로 작업하는 정도에 따라 일종의 예상입니다.