about_Switch — switch 문으로 조건 분기하기
about_Switch — switch 문으로 조건 분기하기
조건이 여러 개일 때마다 if를 이어 붙이고 있었나요? 값 하나를 여러 조건과 비교해야 하는 상황이라면 switch가 한결 간결해져요. 값마다 조건 목록을 나열해 두고, 일치하는 조건이 있으면 해당 동작을 실행하는 방식이에요.
본문
switch가 필요한 순간
스크립트나 함수 안에서 조건을 확인하려면 if 문을 쓰면 돼요. if는 변수의 값, 객체의 속성 값처럼 다양한 조건을 검사할 수 있어요.
그런데 확인해야 할 조건이 여럿이라면 switch 문을 쓰는 게 훨씬 깔끔해요. switch는 일련의 if 문과 비슷하지만 더 단순하죠. 조건과 그에 대응하는 동작을 나열해 두고, 조건이 일치하면 해당 동작을 실행해요.
중요:
switch는 비교 전에 모든 값을 문자열로 변환해요. 이 변환 때문에 예상과 다른 결과가 나올 수 있으니 아래 '문자열 변환의 영향' 부분을 꼭 확인하세요.
구문
기본적인 switch 문은 이런 모양이에요.
switch (<test-expression>) {
<result1-to-be-matched> {<action-statement>}
<result2-to-be-matched> {<action-statement>}
}
이 구조는 사실 다음 if 문들을 나란히 늘어놓은 것과 똑같아요.
if ("$(<result1-to-be-matched>)") -eq ("$(<test-expression>)") {<action>}
if ("$(<result2-to-be-matched>)") -eq ("$(<test-expression>)") {<action>}
조건(<result-to-be-matched>) 자리에는 리터럴 값(문자열이나 숫자), 변수, 그리고 불리언을 돌려주는 스크립트 블록이 올 수 있어요. 비교 전에 모든 값이 문자열로 변환된다는 점, 여기서도 동일하게 적용돼요.
<test-expression>은 표현식 모드에서 평가돼요. 이 표현식이 배열이나 기타 열거 가능한 타입처럼 값을 여러 개 돌려주면, switch는 그 값을 하나씩 나누어 각각 평가해요.
반면 조건(<result-to-be-matched>)은 반드시 단일 값으로 결정되는 표현식이어야 해요. 그 값이 입력값과 비교되는 거죠.
default는 어떤 조건과도 일치하지 않을 때 실행할 동작을 위해 예약된 값이에요.
switch 안에서는 $_와 $switch 자동 변수를 쓸 수 있어요. 이 변수들은 switch에 전달된 표현식의 값을 담고 있고, 각 조건 블록의 스코프 안에서 평가나 사용이 가능해요. 자세한 내용은 about_Automatic_Variables를 참고하세요.
전체 구문은 다음과 같아요.
switch [-Regex | -Wildcard | -Exact] [-CaseSensitive] (<test-expression>) {
string | number | variable | { <value-scriptblock> }
{ <action-statement> }
default { <action-statement> } # optional
}
파일에서 입력을 받는 형태도 있어요.
switch [-Regex | -Wildcard | -Exact] [-CaseSensitive] -File filename {
string | number | variable | { <value-scriptblock> }
{ <action-statement> }
default { <action-statement> } # optional
}
파라미터를 하나도 지정하지 않으면 Exact 파라미터를 쓴 것과 동일하게 동작해요. 즉 대소문자를 구분하지 않는 일치(match)를 수행하죠. 대상 값이 컬렉션이면 각 요소가 나타난 순서대로 하나씩 평가돼요.
switch 문에는 조건을 최소 하나는 반드시 포함해야 해요.
default 절은 값이 어떤 조건과도 일치하지 않을 때 실행돼요. if 문에서의 else와 같은 역할이죠. switch 문 하나에 default 절은 딱 하나만 허용돼요.
switch의 파라미터
Wildcard— 조건이 와일드카드 문자열임을 나타내요. 일치 절이 문자열이 아니면 이 파라미터는 무시돼요. 비교는 대소문자를 구분하지 않아요.Exact— 일치 절이 문자열이라면 정확히 일치해야 함을 나타내요. 일치 절이 문자열이 아니면 이 파라미터는 무시돼요. 비교는 대소문자를 구분하지 않아요.CaseSensitive— 대소문자를 구분해 일치를 수행해요. 일치 절이 문자열이 아니면 이 파라미터는 무시돼요.File—<test-expression>대신 파일에서 입력을 받아요. 파일을 한 줄씩 읽어switch문이 평가하죠. 기본적으로 비교는 대소문자를 구분하지 않아요.File파라미터는 파일 하나만 지원해요.File파라미터가 여러 개 포함되면 마지막 것만 사용돼요. 자세한 내용은 File 파라미터 예제를 참고하세요.Regex— 값과 조건을 정규식으로 일치시켜요. 일치 절이 문자열이 아니면 이 파라미터는 무시돼요. 비교는 대소문자를 구분하지 않아요. 일치 블록 안에서는$Matches자동 변수를 쓸 수 있어요.
참고:
Regex와Wildcard처럼 서로 상충되는 값을 지정하면, 마지막에 지정한 파라미터가 우선하고 나머지 상충 파라미터는 모두 무시돼요. 같은 파라미터를 여러 번 써도 되는데, 역시 마지막에 나열된 것만 사용돼요.
예제
그럼 실제 사용 예제를 보면서 동작 방식을 익혀볼게요.
간단한 일치 예제
아래 예제는 테스트 값 3을 각 조건과 비교해요. 테스트 값이 조건과 일치하면 해당 동작이 실행돼요.
switch (3) {
1 { "It's one." }
2 { "It's two." }
3 { "It's three." }
4 { "It's four." }
}
It's three.
값은 목록의 각 조건과 차례로 비교돼요. 다음 switch 문은 값 3에 대한 조건이 두 개 있는데, 이건 모든 조건이 다 검사된다는 걸 보여주려는 예시예요.
switch (3) {
1 { "It's one." }
2 { "It's two." }
3 { "It's three." }
4 { "It's four." }
3 { "Three again." }
}
It's three.
Three again.
break와 continue로 흐름 제어하기
값이 조건 여러 개에 일치하면 각 조건의 동작이 모두 실행돼요. 이 동작을 바꾸려면 break나 continue 키워드를 쓰면 돼요.
break는 처리를 멈추고switch문을 빠져나와요.continue는 현재 값에 대한 처리를 멈추되, 다음 값들은 계속 처리해요.
다음 예제는 숫자 배열을 처리하면서 각 값이 홀수인지 짝수인지 보여줘요. 음수는 continue로 건너뛰고, 숫자가 아닌 값이 나오면 break로 실행을 끝내요.
switch (1,4,-1,3,"Hello",2,1) {
{$_ -lt 0} { continue }
{$_ -isnot [int32]} { break }
{$_ % 2} { "$_ is Odd" }
{-not ($_ % 2)} { "$_ is Even" }
}
1 is Odd
4 is Even
3 is Odd
문자열 변환의 영향
입력값과 비교값 모두 비교를 위해 문자열로 변환돼요. 의도치 않은 문자열 변환을 피하려면 스크립트 블록을 써서 switch 값을 평가하는 게 좋아요.
switch ( ([datetime]'1 Jan 1970').DayOfWeek ) {
4 { 'The integer value matches a Thursday.' }
"4" { 'The numeric string matches a Thursday.' }
"Thursday" { 'The string value matches a Thursday.' }
{ 4 -eq $_ } { 'The expression matches a Thursday.' }
}
날짜 객체의 DayOfWeek 속성은 열거형(enumeration)이에요. 열거형은 숫자 값이나 문자열 값과 비교할 수 있지만, switch는 이 값을 열거형의 문자열 표현으로 변환해 버려요. 그래서 결과가 이렇게 나와요.
The string value matches a Thursday.
The expression matches a Thursday.
이 동작은 if 문에서의 -eq 비교와는 달라요. 같은 비교를 if로 하면 어떨까요.
if (4 -eq ([datetime]'1 Jan 1970').DayOfWeek) {
'The integer value matches a Thursday.'
}
The value matches a Thursday.
이번엔 해시테이블을 switch에 넘겨볼게요. switch는 해시테이블을 문자열로 변환해요.
$test = @{
Test = 'test'
Test2 = 'test2'
}
$test.ToString()
System.Collections.Hashtable
해시테이블의 문자열 표현이 Test 키의 값과 같지 않다는 점, 잘 보이죠.
switch -Exact ($test) {
'System.Collections.Hashtable' { 'Hashtable string coercion' }
'test' { 'Hashtable value' }
}
Hashtable string coercion
switch로 해시테이블 값 검사하기
이 예제는 해시테이블에 담긴 값의 타입을 검사해요. 값을 검사하기 전에 먼저 해시테이블의 항목을 열거해야 하죠. 문자열 변환의 단점을 피하려면 불리언을 돌려주는 스크립트 블록으로 실행할 동작을 고르는 방식이 좋아요.
$var = @{A = 10; B = 'abc'}
foreach ($key in $var.Keys) {
switch ($var[$key].GetType()) {
{ $_ -eq [int32] } { "$key + 10 = $($var[$key] + 10)" }
{ $_ -eq [string] } { "$key = $($var[$key])" }
}
}
A + 10 = 20
B = abc
switch에서 와일드카드 사용하기
아래 예제는 일치하는 조건이 없어서 출력이 없어요.
switch ("fourteen") {
1 { "It's one."; break }
2 { "It's two."; break }
3 { "It's three."; break }
4 { "It's four."; break }
"fo*" { "That's too many." }
}
default 절을 추가하면 어떤 조건도 성공하지 못했을 때 동작을 실행할 수 있어요.
switch ("fourteen") {
1 { "It's one."; break }
2 { "It's two."; break }
3 { "It's three."; break }
4 { "It's four."; break }
"fo*" { "That's too many." }
default { "No matches" }
}
No matches
fourteen이라는 단어를 조건과 일치시키려면 -Wildcard나 -Regex 파라미터를 써야 해요.
switch -Wildcard ("fourteen") {
1 { "It's one."; break }
2 { "It's two."; break }
3 { "It's three."; break }
4 { "It's four."; break }
"fo*" { "That's too many." }
}
That's too many.
switch에서 정규식 사용하기
다음 예제는 -Regex 파라미터를 사용해요.
$target = 'https://bing.com'
switch -Regex ($target) {
'^ftp\://.*$'
{
"$_ is an ftp address"
break
}
'^\w+@\w+\.com|edu|org$'
{
"$_ is an email address"
break
}
'^(http[s]?)\://.*$'
{
"$_ is a web address that uses $($Matches[1])"
break
}
}
https://bing.com is a web address that uses https
다음 예제는 스크립트 블록을 switch 문의 조건으로 쓰는 모습이에요.
switch ("Test") {
{ $_ -is [string] } { "Found a string" }
"Test" { "This $_ executes as well" }
}
Found a string
This Test executes as well
이번 예제는 날짜 값 두 개를 담은 배열을 처리해요. <value-scriptblock>이 각 날짜의 Year 속성을 비교하고, <action-statement>가 환영 메시지나 2022년 새해가 시작되기까지 남은 일수를 출력해요.
switch ((Get-Date 1-Jan-2022), (Get-Date 25-Dec-2021)) {
{ $_.Year -eq 2021 }
{
$days = ((Get-Date 1/1/2022) - $_).Days
"There are $days days until 2022."
}
{ $_.Year -eq 2022 } { 'Welcome to 2022!' }
}
참고: 설계가 좋지 않거나 악의적으로 만들어진 정규식은 서비스 거부(denial of service)를 일으킬 수 있어요. 자세한 내용은 about_Comparison_Operators의 'Regular expression safety' 섹션을 참고하세요.
switch로 파일 내용 읽기
큰 파일을 한 줄씩 처리할 때는 File 파라미터와 함께 switch를 쓰는 게 효율적이에요. PowerShell이 파일의 줄을 switch 문으로 스트리밍하고, 각 줄을 개별적으로 처리하죠. 파일 끝까지 가기 전에 처리를 끝내고 싶다면 동작 문 안에서 break 키워드를 쓰면 돼요. 큰 파일을 한 줄씩 처리하는 데는 Get-Content보다 switch가 더 효율적이에요.
switch -File은 -Wildcard나 -Regex와 조합해서 유연하고 효율적인 줄 단위 패턴 매칭을 할 수 있어요.
다음 예제는 PowerShell-Docs 저장소의 README.md를 읽으면서, ##으로 시작하는 줄에 도달할 때까지 각 줄을 출력해요.
switch -Regex -File .\README.md {
'^##\s' { break }
default { $_; continue }
}
<filename> 인수는 와일드카드를 받지만 딱 하나의 파일에만 일치해야 해요. 다음 예제는 아까와 같지만 <filename> 인수에 와일드카드를 쓴 경우예요. 와일드카드 패턴이 파일 하나에만 일치하기 때문에 정상 동작해요.
switch -Regex -File .\README.* {
'^##\s' { break }
default { $_; continue }
}
와일드카드로 해석될 수 있는 문자를 리터럴로 취급하고 싶다면 그 문자를 이스케이프해야 해요.
$file = (New-Item -Path 'Temp:\Foo[0]' -Value Foo -Force).FullName
switch -File $file { Foo { 'Foo' } }
# No files matching '...\Temp\Foo[0]' were found.
$fileEscaped = [WildcardPattern]::Escape($file)
switch -File $fileEscaped { foo { 'Foo' } }
# Foo