PowerShell 명령 구문 분석
PowerShell 명령 구문 분석 (about_Parsing)
명령 프롬프트에 명령을 입력하면 PowerShell이 그 텍스트를 어떻게 해석하는지 궁금했던 적 있나요? 커서에 그냥 쳐넣은 문자열이 어떤 때는 숫자로, 어떤 때는 문자열로 처리되는 이유를 설명해 드릴게요. PowerShell은 입력을 "토큰"이라는 단위로 쪼갠 뒤, 각 토큰을 식 모드(expression mode) 와 인자 모드(argument mode) 중 하나로 해석해요. 이 문서는 바로 그 두 모드가 어떻게 동작하는지, 그리고 특수 문자가 나올 때는 어떻게 우회하는지를 다룹니다.
본문
짧은 설명
PowerShell이 명령을 어떻게 구문 분석하는지 설명합니다.
긴 설명
명령 프롬프트에 명령을 입력하면, PowerShell은 그 명령 텍스트를 토큰이라는 일련의 조각으로 쪼갠 다음 각 토큰을 어떻게 해석할지 판단합니다.
예를 들어 다음과 같이 입력하면,
Write-Host book
PowerShell은 이 명령을 Write-Host와 book 두 개의 토큰으로 나누고, 각 토큰을 식 모드와 인자 모드라는 두 가지 주요 구문 분석 모드 중 하나로 독립적으로 해석합니다.
참고: PowerShell은 명령 입력을 구문 분석할 때 명령 이름을 cmdlet 또는 네이티브 실행 파일로 해석하려 시도합니다. 명령 이름이 정확히 일치하지 않으면 PowerShell은 기본 동사로
Get-를 앞에 붙입니다. 예를 들어 PowerShell은Service를Get-Service로 구문 분석합니다. 이 기능은 다음과 같은 이유로 사용하지 않는 게 좋아요.
- 비효율적이에요. PowerShell이 여러 번 검색하게 됩니다.
- 이름이 같은 외부 프로그램이 먼저 해석되기 때문에, 의도한 cmdlet을 실행하지 못할 수 있습니다.
Get-Help와Get-Command는 동사 없는 이름을 인식하지 못해요.- 명령 이름이 예약어 또는 언어 키워드일 수 있어요.
Process는 둘 다에 해당해서Get-Process로 해석될 수 없습니다.
식 모드 (Expression mode)
식 모드는 표현식을 조합하기 위한 모드로, 스크립트 언어에서 값을 다룰 때 필요하죠. 표현식은 PowerShell 구문에서 값을 나타내는 것이며, 단순할 수도 복합적일 수도 있어요.
리터럴 표현식은 값을 그대로 나타냅니다.
'hello'
32
변수 표현식은 참조하는 변수의 값을 지닙니다.
$x
$Script:path
연산자는 다른 표현식을 결합해 평가합니다.
-12
-not $Quiet
3 + 7
$input.Length -gt 1
식 모드에서 다음과 같은 규칙이 적용돼요.
- 문자열 리터럴은 반드시 따옴표로 감싸야 해요.
- 숫자는 (이스케이프하지 않는 한) 일련의 문자로 취급하지 않고 숫자 값으로 처리됩니다.
-,-not같은 단항 연산자와+,-gt같은 이항 연산자를 포함한 연산자는 연산자로 해석되어 피연산자에 각자의 연산을 적용해요.- 특성(attribute) 및 변환 표현식은 표현식으로 구문 분석되어 하위 표현식에 적용됩니다. 예:
[int] '7'. - 변수 참조는 값으로 평가되지만, 스플래팅은 금지되어 구문 분석 오류가 발생해요.
- 그 외의 것은 명령을 호출하는 것으로 취급합니다.
인자 모드 (Argument mode)
구문 분석할 때 PowerShell은 먼저 입력을 표현식으로 해석하려 해요. 그런데 명령 호출을 만나면 파싱이 인자 모드로 계속됩니다. 경로처럼 공백이 포함된 인자가 있다면 그 인자 값을 따옴표로 감싸야 해요.
인자 모드는 셸 환경에서 명령의 인자와 매개변수를 파싱하기 위한 모드입니다. 다음 구문 중 하나를 사용하지 않는 한, 모든 입력은 확장 가능한 문자열로 취급됩니다.
- 달러 기호(
$) 다음에 변수 이름이 오면 변수 참조가 시작되고, 그 외에는 확장 가능한 문자열의 일부로 해석돼요. 변수 참조에는 멤버 액세스나 인덱싱이 포함될 수 있습니다.$HOME처럼 단순한 변수 참조 뒤에 따라오는 추가 문자는 같은 인자의 일부로 간주돼요. 변수 이름을 중괄호({})로 감싸면 뒤따르는 문자와 분리할 수 있습니다. 예:${HOME}. 변수 참조에 멤버 액세스가 포함되면, 추가 문자 중 첫 번째 문자가 새 인자의 시작으로 간주됩니다. 예를 들어$HOME.Length-more는$HOME.Length의 값과 문자열 리터럴-more두 개의 인자로 해석됩니다. - 따옴표(
'와")는 문자열을 시작해요. - 중괄호(
{})는 새 스크립트블록을 시작합니다. - 쉼표(
,)는 배열로 전달되는 리스트를 도입해요. 단, 호출 대상이 네이티브 애플리케이션이라면 확장 가능한 문자열의 일부로 해석됩니다. 맨 앞, 연속, 맨 뒤 쉼표는 지원되지 않아요. - 괄호(
())는 새 표현식을 시작합니다. - 하위 표현식 연산자(
$())는 포함된 표현식을 시작해요. - 맨 앞의 골뱅이(
@)는 스플래팅(@args), 배열(@(1,2,3)), 해시 테이블 리터럴(@{a=1;b=2}) 같은 표현식 구문을 시작합니다. - 토큰의 시작에 오는
(),$(),@()는 표현식이나 중첩 명령을 담을 수 있는 새 파싱 컨텍스트를 만듭니다. 그 뒤에 추가 문자가 오면 첫 번째 추가 문자가 새롭고 별개의 인자의 시작으로 간주됩니다. 따옴표 없는 리터럴 앞에 오는$()는 확장 가능한 문자열처럼 동작하고,()는 표현식인 새 인자를 시작하며,@()는 리터럴@로 받아들여지고()가 표현식인 새 인자를 시작해요. - 그 외의 것은 확장 가능한 문자열로 취급됩니다. 단, 여전히 이스케이프가 필요한 메타문자는 예외예요. 특수 문자 처리를 참고하세요. 인자 모드의 메타문자(특별한 구문적 의미를 지닌 문자)는
<space> ' ", ; ( ) { } | & < > @ #입니다. 이 중< > @ #`는 토큰의 시작 부분에서만 특별합니다. - 파싱 중지 토큰(
--%)은 나머지 모든 인자의 해석을 바꿔요. 자세한 내용은 아래의 파싱 중지 토큰 섹션을 참고하세요.
예제
다음 표는 식 모드와 인자 모드에서 처리된 토큰의 몇 가지 예와 그 평가 결과를 보여줍니다. 이 예제들에서 변수 $a의 값은 4입니다.
| 예제 | 모드 | 결과 |
|---|---|---|
2 |
Expression | 2 (integer) |
`2 |
Expression | "2" (command) |
Write-Output 2 |
Expression | 2 (integer) |
2+2 |
Expression | 4 (integer) |
Write-Output 2+2 |
Argument | "2+2" (string) |
Write-Output(2+2) |
Expression | 4 (integer) |
$a |
Expression | 4 (integer) |
Write-Output $a |
Expression | 4 (integer) |
$a+2 |
Expression | 6 (integer) |
Write-Output $a+2 |
Argument | "4+2" (string) |
$- |
Argument | "$-" (command) |
Write-Output $- |
Argument | "$-" (string) |
a$a |
Expression | "a$a" (command) |
Write-Output a$a |
Argument | "a4" (string) |
a'$a' |
Expression | "a$a" (command) |
Write-Output a'$a' |
Argument | "a$a" (string) |
a"$a" |
Expression | "a$a" (command) |
Write-Output a"$a" |
Argument | "a4" (string) |
a$(2) |
Expression | "a$(2)" (command) |
Write-Output a$(2) |
Argument | "a2" (string) |
모든 토큰은 Boolean이나 String 같은 어떤 객체 유형으로도 해석될 수 있어요. PowerShell은 표현식에서 객체 유형을 결정하려 시도합니다. 객체 유형은 명령이 기대하는 매개변수의 유형과 PowerShell이 인자를 올바른 유형으로 변환하는 방법을 아는지 여부에 따라 달라집니다. 다음 표는 표현식이 반환하는 값에 할당된 유형의 몇 가지 예를 보여줍니다.
| 예제 | 모드 | 결과 |
|---|---|---|
Write-Output !1 |
argument | "!1" (string) |
Write-Output (!1) |
expression | False (Boolean) |
Write-Output (2) |
expression | 2 (integer) |
Set-Variable AB A,B |
argument | 'A','B' (array) |
CMD /CECHO A,B |
argument | 'A,B' (string) |
CMD /CECHO $AB |
expression | 'A B' (array) |
CMD /CECHO :$AB |
argument | ':A B' (string) |
특수 문자 처리
백틱 문자(`)는 표현식에서 어떤 특수 문자든 이스케이프하는 데 사용할 수 있어요. 특히 인자 모드의 메타문자를 리터럴 문자로 쓰고 싶을 때 유용하죠. 예를 들어 확장 가능한 문자열 안에서 달러 기호($)를 리터럴로 쓰고 싶다면,
"The value of `$ErrorActionPreference is '$ErrorActionPreference'."
The value of $ErrorActionPreference is 'Continue'.
줄 연속 (Line continuation)
백틱 문자는 줄 끝에서 사용해 입력을 다음 줄로 이어갈 수도 있어요. 긴 이름과 인자 값을 가진 매개변수가 여러 개인 명령의 가독성을 높이는 데 도움이 되죠. 예를 들어,
New-AzVm `
-ResourceGroupName "myResourceGroupVM" `
-Name "myVM" `
-Location "EastUS" `
-VirtualNetworkName "myVnet" `
-SubnetName "mySubnet" `
-SecurityGroupName "myNetworkSecurityGroup" `
-PublicIpAddressName "myPublicIpAddress" `
-Credential $cred
하지만 줄 연속은 피하는 게 좋아요.
- 백틱 문자는 눈에 잘 안 띄고 잊어버리기 쉬워요.
- 백틱 뒤에 공백이 하나만 더 있으면 줄 연속이 깨집니다. 공백은 눈에 잘 안 띄어서 오류를 찾기 어려울 수 있어요.
PowerShell은 구문상 자연스러운 지점에서 줄을 나눌 수 있는 여러 방법을 제공해요.
- 파이프 문자(
|) 뒤 - 이항 연산자(
+,-,-eq등) 뒤 - 배열의 쉼표(
,) 뒤 [,{,(같은 여는 문자 뒤
매개변수 집합이 크다면 대신 스플래팅을 사용하세요. 예를 들어,
$parameters = @{
ResourceGroupName = "myResourceGroupVM"
Name = "myVM"
Location = "EastUS"
VirtualNetworkName = "myVnet"
SubnetName = "mySubnet"
SecurityGroupName = "myNetworkSecurityGroup"
PublicIpAddressName = "myPublicIpAddress"
Credential = $cred
}
New-AzVm @parameters
네이티브 명령에 인자 전달하기
PowerShell에서 네이티브 명령을 실행하면, 인자가 먼저 PowerShell에 의해 구문 분석됩니다. 그 후 구문 분석된 인자들은 각 매개변수가 공백으로 구분된 하나의 문자열로 합쳐져요.
예를 들어 다음 명령은 icacls.exe 프로그램을 호출합니다.
icacls X:\VMS /grant Dom\HVAdmin:(CI)(OI)F
이 명령을 PowerShell 2.0에서 실행하려면, PowerShell이 괄호를 잘못 해석하지 않도록 이스케이프 문자를 사용해야 해요.
icacls X:\VMS /grant Dom\HVAdmin:`(CI`)`(OI`)F
파싱 중지 토큰 (The stop-parsing token)
PowerShell 3.0부터 파싱 중지(--%) 토큰을 사용해 PowerShell이 입력을 PowerShell 명령이나 표현식으로 해석하는 것을 중단할 수 있어요.
참고: 파싱 중지 토큰은 Windows 플랫폼의 네이티브 명령에서만 사용하도록 의도된 것입니다.
네이티브 명령을 호출할 때는 프로그램 인자 앞에 파싱 중지 토큰을 두면 돼요. 이 방법은 잘못 해석되지 않도록 이스케이프 문자를 사용하는 것보다 훨씬 쉬워요.
파싱 중지 토큰을 만나면 PowerShell은 줄의 나머지 문자를 리터럴로 취급합니다. 유일하게 수행하는 해석은 %USERPROFILE%처럼 표준 Windows 표기법을 사용하는 환경 변수의 값을 대체하는 것뿐이에요.
icacls X:\VMS --% /grant Dom\HVAdmin:(CI)(OI)F
PowerShell은 icacls.exe 프로그램에 다음 명령 문자열을 보냅니다.
X:\VMS /grant Dom\HVAdmin:(CI)(OI)F
파싱 중지 토큰은 다음 개행 문자나 파이프 문자까지만 유효해요. 줄 연속 문자(`)로 그 효과를 연장하거나 명령 구분자(;)로 끝낼 수는 없습니다.
%variable% 환경 변수 참조 외에는 다른 동적 요소를 명령에 포함할 수 없어요. 배치 파일에서처럼 %를 %%로 이스케이프하는 것도 지원되지 않습니다. %<name>% 토큰은 반드시 확장됩니다. <name>이 정의된 환경 변수를 가리키지 않으면 그 토큰은 그대로 전달돼요.
>file.txt 같은 스트림 리디렉션은 사용할 수 없어요. 왜냐하면 대상 명령의 인자로 그대로 전달되기 때문이죠.
다음 예에서 첫 단계는 파싱 중지 토큰을 사용하지 않고 명령을 실행합니다. PowerShell이 따옴표로 묶인 문자열을 평가해 cmd.exe에 (따옴표 없는) 값을 전달하고, 그 결과 오류가 발생합니다.
PS> cmd /c echo "a|b"
'b' is not recognized as an internal or external command,
operable program or batch file.
PS> cmd /c --% echo "a|b"
"a|b"
참고: PowerShell cmdlet을 사용할 때는 파싱 중지 토큰이 필요 없어요. 하지만 그 인자들로 네이티브 명령을 호출하도록 설계된 PowerShell 함수에 인자를 전달할 때는 유용할 수 있습니다.
따옴표 문자를 포함하는 인자 전달하기
경고: Windows에서 배치 파일에 인자를 전달하면 인자가
cmd.exe에 원시 명령줄 문자열로 전달됩니다. PowerShell과 그 기반 API가 매개변수를 안전하게 해석하려 시도하지만, 신뢰할 수 없는 입력은 다른 방식으로 전달해야 해요.
일부 네이티브 명령은 따옴표 문자를 포함하는 인자를 기대합니다. PowerShell 7.3은 네이티브 명령을 위한 명령줄 파싱 방식을 변경했어요.
주의: 새 동작은 Windows PowerShell 5.1 동작에서의 호환성이 깨지는 변경입니다. 네이티브 애플리케이션 호출 시 여러 문제를 우회했던 스크립트와 자동화를 깨뜨릴 수 있어요. 필요한 경우 파싱 중지 토큰(
--%)이나Start-Processcmdlet을 사용해 네이티브 인자 전달을 피하세요.
새 $PSNativeCommandArgumentPassing 기본 설정 변수가 이 동작을 제어합니다. 이 변수를 사용하면 런타임에 동작을 선택할 수 있어요. 유효한 값은 Legacy, Standard, Windows입니다. 기본 동작은 플랫폼별로 다릅니다. Windows 플랫폼에서는 기본 설정이 Windows이고, 비 Windows 플랫폼에서는 기본값이 Standard예요.
Legacy는 기존의 동작입니다. Windows와 Standard 모드의 동작은 동일한데, 단 Windows 모드에서는 다음 파일에 대한 호출이 자동으로 Legacy 스타일의 인자 전달을 사용한다는 점만 다릅니다.
cmd.execscript.exewscript.exe.bat로 끝나는 파일.cmd로 끝나는 파일.js로 끝나는 파일.vbs로 끝나는 파일.wsf로 끝나는 파일
$PSNativeCommandArgumentPassing이 Legacy나 Standard로 설정되어 있으면 파서는 이 파일들을 검사하지 않아요.
참고: 다음 예제들은
TestExe.exe도구를 사용합니다.TestExe는 소스 코드에서 빌드할 수 있어요. PowerShell 소스 저장소의 TestExe를 참고하세요.
이 변경으로 가능해진 새로운 동작들입니다.
- 포함된 따옴표가 있는 리터럴 또는 확장 가능한 문자열의 따옴표가 이제 보존됩니다.
PS> $a = 'a" "b' PS> TestExe -echoargs $a 'c" "d' e" "f Arg 0 is <a" "b> Arg 1 is <c" "d> Arg 2 is <e f> - 인자로 전달된 빈 문자열이 이제 보존됩니다.
PS> TestExe -echoargs '' a b '' Arg 0 is <> Arg 1 is <a> Arg 2 is <b> Arg 3 is <>
이 예제들의 목표는 (공백과 따옴표가 있는) 디렉터리 경로 "C:\Program Files (x86)\Microsoft\"를 네이티브 명령에 전달해, 그 경로를 따옴표로 묶인 문자열로 받게 하는 것입니다.
Windows 또는 Standard 모드에서 다음 예제들은 기대한 결과를 만들어냅니다.
TestExe -echoargs """${Env:ProgramFiles(x86)}\Microsoft\"""
TestExe -echoargs '"C:\Program Files (x86)\Microsoft\"'
Legacy 모드에서 같은 결과를 얻으려면 따옴표를 이스케이프하거나 파싱 중지 토큰(--%)을 사용해야 해요.
TestExe -echoargs """"${Env:ProgramFiles(x86)}\Microsoft\\""""
TestExe -echoargs "\""C:\Program Files (x86)\Microsoft\\"""
TestExe -echoargs --% ""\"C:\Program Files (x86)\Microsoft\\\"\"""
TestExe -echoargs --% """C:\Program Files (x86)\Microsoft\\"""
TestExe -echoargs --% """%ProgramFiles(x86)%\Microsoft\\"""
참고: 백슬래시(
\) 문자는 PowerShell에서 이스케이프 문자로 인식되지 않아요. 이스케이프 문자는 기반 API인 ProcessStartInfo.ArgumentList가 사용하는 문자예요.
PowerShell 7.3은 네이티브 명령의 매개변수 바인딩을 추적하는 기능도 추가했어요. 자세한 내용은 Trace-Command를 참고하세요.
PowerShell 명령에 인자 전달하기
PowerShell 3.0부터 매개변수 종료 토큰(--)을 사용해 PowerShell이 입력을 PowerShell 매개변수로 해석하는 것을 중단할 수 있어요. 이는 POSIX 셸 및 유틸리티 규격에서 지정된 관례입니다.
매개변수 종료 토큰 (The end-of-parameters token)
매개변수 종료 토큰(--)은 그 뒤에 오는 모든 인자가 마치 이중 따옴표로 둘러싸인 것처럼 실제 형태 그대로 전달되도록 합니다. 예를 들어 --를 사용하면 따옴표 없이, 매개변수로 해석되지 않고 -InputObject 문자열을 출력할 수 있어요.
Write-Output -- -InputObject
-InputObject
파싱 중지(--%) 토큰과 달리, -- 토큰 뒤에 오는 값은 PowerShell이 표현식으로 해석할 수 있어요.
Write-Output -- -InputObject $Env:PROCESSOR_ARCHITECTURE
-InputObject
AMD64
이 동작은 PowerShell 명령에만 적용됩니다. 외부 명령을 호출할 때 -- 토큰을 사용하면, 그 -- 문자열이 인자로 그 명령에 전달돼요.
TestExe -echoargs -a -b -- -c
출력은 --가 TestExe에 인자로 전달되었음을 보여줍니다.
Arg 0 is <-a>
Arg 1 is <-b>
Arg 2 is <-->
Arg 3 is <-c>
물결표 (~)
물결표 문자(~)는 PowerShell에서 특별한 의미를 지닙니다. PowerShell 명령과 함께 경로의 시작 부분에서 사용하면, PowerShell은 물결표 문자를 사용자의 홈 디렉터리로 확장해요. 경로의 다른 곳에서 물결표 문자를 사용하면 리터럴 문자로 취급됩니다.
PS D:\temp> $PWD
Path
----
D:\temp
PS D:\temp> Set-Location ~
PS C:\Users\user2> $PWD
Path
----
C:\Users\user2
이 예제에서 New-Item의 Name 매개변수는 문자열을 기대합니다. 물결표 문자는 리터럴 문자로 취급됩니다. 새로 만든 디렉터리로 이동하려면 물결표 문자로 경로를 한정해야 해요.
PS D:\temp> Set-Location ~
PS C:\Users\user2> New-Item -Type Directory -Name ~
Directory: C:\Users\user2
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 5/6/2024 2:08 PM ~
PS C:\Users\user2> Set-Location ~
PS C:\Users\user2> Set-Location .\~
PS C:\Users\user2\~> $PWD
Path
----
C:\Users\user2\~
PowerShell 7.5-preview.2는 네이티브 명령에 대해 물결표를 사용자의 홈 디렉터리로 확장하는 실험 기능을 추가했어요. 자세한 내용은 Using Experimental Features in PowerShell의 PSNativeWindowsTildeExpansion 기능을 참고하세요.
확장된 문자열은 네이티브 명령에 전달됩니다. 물결표를 확장함으로써 PowerShell은 물결표 문자를 지원하지 않는 Windows의 네이티브 명령에서 발생하는 오류를 방지해요. Trace-Command로 매개변수 바인딩을 추적하면 결과 문자열을 볼 수 있습니다.
Trace-Command -Name ParameterBinding -Expression {
findstr /C:\foo" ~\repocache.clixml
} -PSHost
DEBUG: 2024-05-06 15:13:46.8268 ParameterBinding Information: 0 : BIND NAMED native application line args [C:\Windows\system32\findstr.exe]
DEBUG: 2024-05-06 15:13:46.8270 ParameterBinding Information: 0 : BIND cmd line arg [/C:\oo] to position [0]
DEBUG: 2024-05-06 15:13:46.8271 ParameterBinding Information: 0 : BIND cmd line arg [C:\Users\user2\repocache.clixml] to position [1]
DEBUG: 2024-05-06 15:13:46.8322 ParameterBinding Information: 0 : CALLING BeginProcessing
~\repocache.clixml이 C:\Users\user2\repocache.clixml로 확장된 것에 주목하세요.