about_Switch — switch 문으로 조건 분기하기

about_Switch — switch 문으로 조건 분기하기

조건이 여러 개일 때마다 if를 이어 붙이고 있었나요? 값 하나를 여러 조건과 비교해야 하는 상황이라면 switch가 한결 간결해져요. 값마다 조건 목록을 나열해 두고, 일치하는 조건이 있으면 해당 동작을 실행하는 방식이에요.

출처: Microsoft Learn — about_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 자동 변수를 쓸 수 있어요.

참고: RegexWildcard처럼 서로 상충되는 값을 지정하면, 마지막에 지정한 파라미터가 우선하고 나머지 상충 파라미터는 모두 무시돼요. 같은 파라미터를 여러 번 써도 되는데, 역시 마지막에 나열된 것만 사용돼요.

예제

그럼 실제 사용 예제를 보면서 동작 방식을 익혀볼게요.

간단한 일치 예제

아래 예제는 테스트 값 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로 흐름 제어하기

값이 조건 여러 개에 일치하면 각 조건의 동작이 모두 실행돼요. 이 동작을 바꾸려면 breakcontinue 키워드를 쓰면 돼요.

  • 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

더 알아보기