해시 테이블의 모든 것을 알고 싶었습니다.

한 걸음 뒤로 물러서서 해시 테이블에 대해 이야기하고 싶습니다. 나는 지금 항상 그들을 사용합니다. 어젯밤 사용자 그룹 모임이 끝난 후 누군가에 대해 가르치고 있었고, 나는 그가 가진 것과 똑같은 혼란을 가지고 있다는 것을 깨달았다. 해시 테이블은 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
}

열거자는 각 키/값 쌍을 하나씩 제공합니다. 이 사용 사례를 위해 특별히 설계되었습니다. 이 사실을 알려준 Mark Kraus에게 감사의 말을 전합니다.

BadEnumeration

한 가지 중요한 세부 정보는 해시 테이블이 열거되는 동안에는 수정할 수 없다는 것입니다. 기본 $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 ){...}

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

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

이 문제는 값이 0이지만 $null 및 존재하지 않는 키에 대해서는 해당 문제를 해결합니다. 대부분의 경우 이러한 차이점을 구분할 필요는 없지만, 그렇게 할 때의 기능이 있습니다.

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

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

키 제거 및 지우기

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

$person.remove('age')

값을 할당하면 $null 값이 있는 키가 남게 $null 됩니다.

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

$person = @{}

작동 하는 동안 대신 함수를 clear() 사용 하려고 합니다.

$person.clear()

이는 함수를 사용하여 자체 문서화 코드를 만들고 코드의 의도를 매우 클린 만드는 인스턴스 중 하나입니다.

모든 재미있는 물건

순서가 지정된 해시 테이블

기본적으로 해시 테이블은 순서가 지정되지(또는 정렬되지) 않습니다. 기존 환경에서는 항상 키를 사용하여 값에 액세스할 때는 순서가 중요하지 않습니다. 속성이 정의한 순서대로 유지되도록 할 수 있습니다. 고맙게도, 키워드(keyword) 그렇게 ordered 할 수있는 방법이 있습니다.

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

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

인라인 해시 테이블

해시 테이블을 한 줄에 정의할 때는 세미콜론을 이용해 키/값 쌍을 구분할 수 있습니다.

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

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

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

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

$property = @{
    name = 'totalSpaceGB'
    expression = { ($_.used + $_.free) / 1GB }
}

name cmdlet은 해당 열에 레이블을 지정합니다. 파이프 expression 에 있는 개체의 값이 있는 스크립트 $_ 블록입니다. 작동 중인 스크립트는 다음과 같습니다.

$drives = Get-PSDrive | Where Used
$drives | Select-Object -Property name, $property

Name     totalSpaceGB
----     ------------
C    238.472652435303

나는 변수에 배치하지만 쉽게 인라인으로 정의 할 수 있으며, 당신은 당신이 그것에있는 동안 단축 namenexpressione 수 있습니다.

$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

대신 기호 $@ 사용하면 splat 작업이 호출됩니다.

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

명령이 너무 길어질 때마다 스플래팅을 사용합니다. 창이 오른쪽으로 스크롤되도록 너무 오래 정의합니다. 함수에 대한 세 가지 속성을 적중하면 스플래트된 해시 테이블을 사용하여 다시 작성할 확률이 높습니다.

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

스플래팅을 사용하는 가장 일반적인 방법 중 하나는 스크립트의 다른 위치에서 제공되는 선택적 매개 변수를 처리하는 것입니다. 선택적 $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

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

깔끔한 코드의 스플래팅

클린er를 코딩하는 경우 단일 매개 변수를 스플래팅하는 데 아무런 문제가 없습니다.

$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}

이러한 항목을 살펴보기 위한 명령으로 이동하는 것은 매우 클린 다른 항목에서 JSON을 자주 사용하기 때문입니다ConvertTo-JSON.

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

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

개체 만들기

개체만 필요하며 해시 테이블을 사용하여 속성을 보유하는 것만으로는 작업을 완료할 수 없을 때도 있습니다. 가장 일반적으로 키를 열 이름으로 표시하려고 합니다. A 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] 하는 경우 해당 명령과 Import-CliXml 명령을 사용해야 Export-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 참조하세요.

PSBoundParameters gotcha

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

$PSDefaultParameterValues

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

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

UTF8Out-File -Encoding 매개 변수의 기본값으로 설정하는 $PSDefaultParameterValues 해시 테이블에 항목을 추가합니다. 세션별로 추가되므로 $profile에 배치해야 합니다.

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

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

또한 값을 대량으로 설정할 수 있도록 wild카드를 허용합니다. 다음은 대표적인 사용 방법입니다.

$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로 작업하는 정도에 따라 일종의 예상입니다.