about_Splatting

about_Splatting

명령어에 넘길 인자를 담아둔 변수(collection)를 통째로 전달하는 스플래팅(splatting) 에 대해 알아볼게요. PowerShell에서 명령어에 매개변수를 준다는 건 매번 -Key value처럼 하나하나 타이핑하는 일인데, 자주 쓰는 값 묶음이라면 굳이 그렇게 하지 않아도 돼요. 값들을 모아둔 변수를 @변수명 형태로 던져주기만 하면, PowerShell이 그 묶음을 알아서 매개변수에 하나씩 풀어 넣어 줘요. 특히 같은 옵션을 여러 명령어에 반복해서 쓸 때 이 방식이 정말 유용하답니다.

출처: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting

본문

스플래팅은 매개변수 값들의 묶음(collection)을 하나의 단위로 명령어에 넘기는 방법이에요. PowerShell은 그 묶음 안의 각 값을 명령어의 매개변수와 연결해요. 스플래팅된 값은 이름 붙은 스플래팅 변수에 저장하는데, 생김새는 평범한 변수와 같아 보이지만 달러 기호($) 대신 At 기호(@)로 시작해요. 이 @가 "값 하나가 아니라 값들의 묶음을 넘긴다"는 신호인 셈이에요.

이 방식의 장점은 명령어가 짧아지고 읽기 편해진다는 점이에요. 스플래팅 값을 여러 명령어 호출에서 재사용할 수도 있고, $PSBoundParameters 자동 변수에 들어 있는 매개변수 값을 다른 스크립트나 함수로 넘길 때도 써요.

스플래팅은 Windows PowerShell 2.0에서 처음 도입됐어요.

문법

<CommandName> <optional parameters> @<HashTable> <optional parameters>
<CommandName> <optional parameters> @<Array> <optional parameters>

매개변수 이름을 안 써도 되는 위치(포지셔널) 매개변수에 값을 넘길 때는 배열 문법을 쓰고, 매개변수 이름과 값을 짝지어 넘길 때는 해시 테이블 문법을 써요. 스플래팅 값을 매개변수 목록의 어느 위치에 두어도 상관없어요.

스플래팅을 쓸 때 반드시 모든 매개변수를 해시 테이블이나 배열로 넘겨야 하는 건 아니에요. 일부는 스플래팅으로, 나머지는 위치나 매개변수 이름으로 넘길 수도 있어요. 또 한 명령어에 여러 개의 스플래팅 개체를 써서 각 매개변수에 값을 두 번 넘기지 않도록 할 수도 있어요.

PowerShell 7.1부터는 명령어에서 매개변수를 명시적으로 정의하면 스플래팅된 매개변수를 덮어쓸 수 있어요.

해시 테이블로 스플래팅하기

매개변수 이름과 값의 쌍을 스플래팅하려면 해시 테이블을 쓰면 돼요. 이 형식은 모든 매개변수 타입에 쓸 수 있는데, [switch] 매개변수와 포지셔널 매개변수까지 포함해요. 단, 포지셔널 매개변수는 이름으로 지정해줘야 해요.

아래 두 명령어를 비교해볼게요. 둘 다 같은 디렉터리에서 Test.txt 파일을 Test2.txt로 복사하는 Copy-Item 명령어예요.

첫 번째는 매개변수 이름을 일일이 적는 전통적인 방식이에요.

Copy-Item -Path "test.txt" -Destination "test2.txt" -WhatIf

두 번째는 해시 테이블 스플래팅을 쓰는 경우예요. 먼저 매개변수 이름과 값 쌍으로 된 해시 테이블을 만들어 $HashArguments 변수에 저장하고, 다음 명령어에서 이 변수를 스플래팅해 사용해요. 명령어 안에서 달러 기호($HashArguments) 대신 At 기호(@HashArguments)를 쓰는 게 포인트예요.

WhatIf [switch] 매개변수에 값을 주려면 $true$false를 쓰면 돼요.

$HashArguments = @{
Path = "test.txt"
Destination = "test2.txt"
WhatIf = $true
}
Copy-Item @HashArguments

여기서 주의할 점이 하나 있어요. 위 예시의 첫 번째 명령어에서는 @가 스플래팅된 값이 아니라 그냥 해시 테이블을 뜻해요. PowerShell에서 해시 테이블의 문법은 @{키 = 값; ...} 형태랍니다.

배열로 스플래팅하기

포지셔널 매개변수에 값을 넘길 때는 배열을 쓰면 돼요. 이런 매개변수는 이름이 필요 없기 때문이에요. 배열 안에서 값은 위치 번호 순서대로 정렬되어 있어야 해요. 배열 스플래팅으로 네이티브 명령어에 값을 넘길 수도 있어요.

아래는 Windows에서 네이티브 명령어에 배열 스플래팅으로 값을 넘기는 예시예요. cmd.exe 명령어에 /c, dir, /ogn 세 인자가 전달돼요.

$array = '/c', 'dir', '/ogn'
cmd.exe @array

이번에도 같은 디렉터리에서 Test.txtTest2.txt로 복사하는 Copy-Item 명령어 두 개를 비교해볼게요.

첫 번째는 매개변수 이름을 생략한 전통적인 방식이에요. 값들이 명령어 안에 위치 순서대로 나와요.

Copy-Item "test.txt" "test2.txt" -WhatIf

두 번째는 배열 스플래팅을 쓴 경우예요. 매개변수 값들을 배열로 만들어 $ArrayArguments 변수에 저장하고, 명령어에서 이 변수를 스플래팅해 사용해요. 달러 기호($ArrayArguments) 대신 At 기호(@ArrayArguments)를 쓰는 것만 다르죠.

$ArrayArguments = "test.txt", "test2.txt"
Copy-Item @ArrayArguments -WhatIf

지그재그 배열(jagged array) 은 안에 또 배열을 담고 있는 배열이에요. PowerShell은 네이티브 명령어에 지그재그 배열을 스플래팅할 때와 PowerShell 명령어에 스플래팅할 때를 다르게 처리해요.

예를 들어 아래 명령어는 지그재그 배열을 PowerShell 스크립트에 스플래팅해요.

$arguments = 1, (2, 3), 4
D:\temp\testargs.ps1 @arguments

스크립트는 인자 3개를 받는데, 그중 두 번째가 배열이에요.

3 argument(s) received (enclosed in  for delineation):

이번에는 지그재그 배열을 네이티브 명령어에 스플래팅한 경우예요.

$arguments = 1, (2, 3), 4
echoargs.exe @arguments

네이티브 명령어는 인자 4개를 받는데, 두 번째와 세 번째 항목이 안쪽 배열의 요소들이에요.

4 argument(s) received (enclosed in  for delineation):

ArgumentList 매개변수 사용하기

여러 cmdlet에는 ArgumentList라는 매개변수가 있어요. 이 매개변수는 cmdlet이 실행하는 스크립트블록에 매개변수 값을 넘기는 데 쓰여요. ArgumentList 매개변수는 스크립트블록에 전달될 값들의 배열을 받아요. PowerShell은 사실 이때 배열 스플래팅을 써서 그 값들을 스크립트블록의 매개변수에 바인딩하는 거예요. 그런데 ArgumentList를 쓸 때, 배열 하나를 통째로 단일 매개변수에 바인딩해서 넘겨야 한다면, 그 배열을 또 다른 배열의 유일한 요소로 감싸줘야 해요.

아래 예시는 문자열 배열 하나를 매개변수로 받는 스크립트블록을 다뤄요.

$array = 'Hello', 'World!'
Invoke-Command -ScriptBlock {
param([string[]]$Words) $Words -join ' '
} -ArgumentList $array

이 예시에서는 $array의 첫 번째 항목만 스크립트블록에 전달돼요.

Hello

배열을 감싸면 이렇게 돼요.

$array = 'Hello', 'World!'
Invoke-Command -ScriptBlock {
param([string[]]$Words) $Words -join ' '
} -ArgumentList (,$array)

이번에는 $array가 배열로 감싸져서, 전체 배열이 단일 개체로 스크립트블록에 전달돼요.

Hello World!

예시

예시 1: 여러 명령어에서 스플래팅 매개변수 재사용

이 예시는 스플래팅된 값을 여러 명령어에서 재사용하는 법을 보여줘요. 이 예시의 명령어들은 Write-Host cmdlet으로 호스트 프로그램 콘솔에 메시지를 출력해요. 전경색과 배경색을 스플래팅으로 지정해요.

모든 명령어의 색을 바꾸고 싶다면 $Colors 변수의 값만 바꾸면 돼요.

첫 번째 명령어는 매개변수 이름과 값을 가진 해시 테이블을 만들어 $Colors 변수에 저장해요.

$Colors = @{ForegroundColor = "black"; BackgroundColor = "white"}

두 번째와 세 번째 명령어는 Write-Host 명령어에서 $Colors 변수를 스플래팅에 사용해요. 이때 달러 기호($Colors) 대신 At 기호(@Colors)를 쓰면 돼요.

#Write a message with the colors in $Colors
Write-Host "This is a test." @Colors

#Write second message with same colors. The position of splatted
#hash table doesn't matter.
Write-Host @Colors "This is another test."

예시 2: $PSBoundParameters로 매개변수 전달하기

이 예시는 스플래팅과 $PSBoundParameters 자동 변수를 써서 매개변수를 다른 명령어로 전달하는 법을 보여줘요.

$PSBoundParameters 자동 변수는 사전(dictionary) 개체(System.Collections.Generic.Dictionary)로, 스크립트나 함수가 실행될 때 사용된 모든 매개변수 이름과 값을 담고 있어요.

아래 예시에서는 $PSBoundParameters 변수를 써서 Test2 함수가 받은 매개변수 값을 Test1 함수로 전달해요. Test2에서 Test1 함수를 호출하는 두 곳 모두 스플래팅을 써요.

function Test1
{
param($a, $b, $c)

"a = $a"
"b = $b"
"c = $c"
}

function Test2
{
param($a, $b, $c)

#Call the Test1 function with $a, $b, and $c.
Test1 @PSBoundParameters

#Call the Test1 function with $b and $c, but not with $a
Test1 -b $PSBoundParameters.b -c $PSBoundParameters.c
}

Test2 -a 1 -b 2 -c 3
a = 1
b = 2
c = 3
a =
b = 2
c = 3

예시 3: 명시적으로 정의한 매개변수로 스플래팅 매개변수 덮어쓰기

이 예시는 명시적으로 정의한 매개변수로 스플래팅된 매개변수를 덮어쓰는 법을 보여줘요. 스플래팅에 쓸 해시 테이블을 새로 만들거나 그 안의 값을 바꾸고 싶지 않을 때 유용해요.

$commonParams 변수는 East US 위치에 가상 머신을 만들기 위한 매개변수들을 담고 있어요. $allVms 변수는 만들 가상 머신들의 목록이에요. 목록을 반복하면서 $commonParams로 스플래팅해 각 가상 머신을 만들어요. 그런데 myVM2만 다른 리전에 만들고 싶은 상황이에요. $commonParams 해시 테이블을 수정하는 대신, New-AzVm에서 Location 매개변수를 명시적으로 정의해 $commonParams 안의 Location 키 값을 덮어쓸 수 있어요.

$commonParams = @{
ResourceGroupName = "myResourceGroup"
Location = "East US"
VirtualNetworkName = "myVnet"
SubnetName = "mySubnet"
SecurityGroupName = "myNetworkSecurityGroup"
PublicIpAddressName = "myPublicIpAddress"
}

$allVms = @('myVM1','myVM2','myVM3',)

foreach ($vm in $allVms)
{
if ($vm -eq 'myVM2')
{
New-AzVm @commonParams -Name $vm -Location "West US"
}
else
{
New-AzVm @commonParams -Name $vm
}
}

예시 4: 한 명령어에서 여러 스플래팅 개체 사용하기

한 명령어에서 여러 개의 스플래팅 개체를 사용할 수 있어요. 이 예시에서는 서로 다른 매개변수를 각각의 해시 테이블에 정의하고, 그 해시 테이블들을 하나의 Write-Host 명령어에서 스플래팅해요.

$a = @{
Message         = 'Hello', 'World!'
}
$b = @{
Separator       = '|'
}
$c = @{
BackgroundColor = 'Cyan'
ForegroundColor = 'Black'
}
Write-Host @a @b @c

명령어 매개변수 스플래팅하기

스플래팅으로 명령어의 매개변수를 그대로 나타낼 수도 있어요. 이 기법은 다른 명령어를 호출하는 프록시 함수(proxy function)를 만들 때 특히 유용해요. 이 기능은 Windows PowerShell 3.0에서 도입됐어요.

명령어의 매개변수를 스플래팅하려면 @args를 써서 명령어 매개변수를 나타내면 돼요. 이 기법은 매개변수를 일일이 나열하는 것보다 편하고, 호출 대상 명령어의 매개변수가 바뀌어도 수정 없이 동작해요.

이 기능은 할당되지 않은 모든 매개변수 값을 담고 있는 $args 자동 변수를 사용해요.

예를 들어 아래 함수는 Get-Process cmdlet을 호출해요. 이 함수에서 @argsGet-Process cmdlet의 모든 매개변수를 나타내요.

function Get-MyProcess { Get-Process @args }

Get-MyProcess 함수를 사용하면 할당되지 않은 모든 매개변수와 값이 @args에 전달돼요. 아래 명령어들이 그 예시예요.

Get-MyProcess -Name powershell
Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
463      46   225484     237196   719    15.86   3228 powershell
Get-MyProcess -Name powershell_ise -FileVersionInfo
ProductVersion   FileVersion      FileName
--------------   -----------      --------
6.2.9200.16384   6.2.9200.1638... C:\Windows\system32\WindowsPowerShell\...

@args는 명시적으로 선언된 매개변수가 있는 함수에서도 쓸 수 있어요. 함수 안에서 여러 번 쓸 수도 있지만, 입력한 매개변수는 모두 모든 @args 인스턴스에 전달돼요. 아래 예시를 볼게요.

function Get-MyCommand
{
param ([switch]$P, [switch]$C)
if ($P) { Get-Process @args }
if ($C) { Get-Command @args }
}

Get-MyCommand -P -C -Name powershell
NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
------    -----      -----     ------      --  -- -----------
50   112.76      78.52      16.64    6880   1 powershell

Path               : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Extension          : .exe
Definition         : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Source             : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Version            : 10.0.22621.3085
Visibility         : Public
OutputType         : {System.String}
Name               : powershell.exe
CommandType        : Application
ModuleName         :
Module             :
RemotingCapability : PowerShell
Parameters         :
ParameterSets      :
HelpUri            :
FileVersionInfo    : File:             C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
InternalName:     POWERSHELL
OriginalFilename: PowerShell.EXE.MUI
FileVersion:      10.0.22621.1 (WinBuild.160101.0800)
FileDescription:  Windows PowerShell
Product:          Microsoft® Windows® Operating System
ProductVersion:   10.0.22621.1
Debug:            False
Patched:          False
PreRelease:       False
PrivateBuild:     False
SpecialBuild:     False
Language:         English (United States)

주의할 점

CmdletBinding 또는 Parameter 특성을 써서 함수를 고급 함수(advanced function)로 만들면, 그 함수에서는 $args 자동 변수를 더는 쓸 수 없어요. 고급 함수는 매개변수를 명시적으로 정의해야 해요.

PowerShell Desired State Configuration(DSC)은 스플래팅을 쓰도록 설계되지 않았어요. DSC 리소스에 값을 전달할 때는 스플래팅을 쓸 수 없어요. 자세한 내용은 Gael Colas의 글 Pseudo-Splatting DSC Resources를 참고하세요.

더 알아보기