PowerShell 정규식
PowerShell 정규식 (about_Regular_Expressions)
정규식은 텍스트에서 특정 패턴을 찾을 때 쓰는 도구예요. 매번 문자열마다 조건을 붙여 비교하는 대신, 한 번만 그려두면 같은 모양의 텍스트를 몽땅 찾아낼 수 있죠. PowerShell은 이 정규식 엔진으로 .NET 정규식 엔진을 사용해요.
본문
정규식은 글자 그대로의 문자, 연산자, 여러 구성 요소가 모여 만든 패턴이에요. 이 문서에서는 PowerShell에서 정규식을 어떻게 쓰는지 예시와 함께 살펴볼게요. 정규식을 사용하는 연산자와 cmdlet은 아래 링크에서 더 자세히 다뤄요.
PowerShell 정규식은 기본적으로 대소문자를 구분하지 않아요. 위에서 본 각 방법은 대소문자를 구분하게 만드는 방식이 조금씩 달라요.
Select-String은 CaseSensitive 매개 변수를 써요.- 정규식을 쓰는 연산자는 대소문자 구분 버전인
-cmatch,-creplace,-csplit을 써요. switch문에서는-CaseSensitive옵션을 써요.
문자 리터럴
정규식은 글자 하나일 수도, 문자열일 수도 있어요. 이 표현식은 지정한 텍스트를 정확히 그대로 일치시키라고 엔진에 지시해요.
# 'book'에 "oo"라는 문자열이 들어 있어서 True를 반환해요.
'book' -match 'oo'
문자 클래스
정확한 패턴을 알고 있을 때는 문자 리터럴이 효과적이지만, 조금은 덜 정확하게 맞추고 싶을 때가 있죠. 그때는 문자 클래스를 써요.
문자 그룹
[문자 그룹]은 괄호 안 문자 중 하나와 한 번 일치시켜요. [^문자 그룹]은 반대로, 그룹에 없는 문자와만 일치해요.
# 이 표현식은 big, bog, bug 중 하나에만 맞아도 True를 반환해요.
'big' -match 'b[iou]g'
일치시키려는 문자 목록에 하이픈(-)이 들어 있다면, 그 하이픈은 목록의 맨 앞 또는 맨 뒤에 놓아야 해요. 그래야 문자 범위 표기와 구분되거든요.
문자 범위
패턴은 문자의 범위가 될 수도 있어요. 알파벳 [A-Z], 숫자 [0-9], 심지어 ASCII 기반 [ -~](출력 가능한 모든 문자)까지 지정할 수 있어요.
# 이 표현식은 두 자리 숫자에 일치하면 True를 반환해요.
42 -match '[0-9][0-9]'
숫자
\d 문자 클래스는 10진수 숫자 하나와 일치해요. 반대로 \D는 숫자가 아닌 문자와 일치하죠.
# 서버 이름(Server-01 ~ Server-99)에 일치하면 True를 반환해요.
'Server-01' -match 'Server-\d\d'
단어 문자
\w 문자 클래스는 단어 문자 [a-zA-Z_0-9]와 일치해요. 단어 문자가 아닌 것과는 \W를 써요.
# 첫 단어 문자 'B'에 일치해서 True를 반환해요.
'Book' -match '\w'
와일드카드
마침표(.)는 정규식에서 와일드카드 문자에요. 줄바꿈(\n)을 제외한 어떤 문자와도 일치해요.
# 줄바꿈을 제외한 어떤 4개 문자에든 일치해서 True를 반환해요.
'a1\ ' -match '....'
공백
공백 문자 하나는 \s 문자 클래스로, 공백이 아닌 문자는 \S로 일치시켜요. 글자 그대로의 공백을 일치시키려면 리터럴 공백을 쓰면 돼요.
# 앞 공백은 공백 문자 클래스로, 뒤 공백은 리터럴 공백으로 일치시켜서 True를 반환해요.
' - ' -match '\s- '
수량자 (Quantifier)
수량자는 입력 문자열에 각 요소가 몇 번 등장해야 하는지를 정해요. PowerShell에서 쓰는 대표적인 수량자는 이래요.
| 수량자 | 설명 |
|---|---|
* |
0번 이상 |
+ |
1번 이상 |
? |
0번 또는 1번 |
{n,m} |
최소 n번, 최대 m번 |
별표(*)는 앞 요소와 0번 이상 일치해요. 덕분에 그 요소가 아예 없어도 일치로 볼 수 있죠.
# 계정 이름이 아예 없어도 모든 계정 이름 문자열에 True를 반환해요.
'ACCOUNT NAME: Administrator' -match 'ACCOUNT NAME:\s*\w*'
더하기(+)는 앞 요소와 1번 이상 일치해요.
# 서버 이름에 일치하면 True를 반환해요.
'DC-01' -match '[A-Z]+-\d\d'
물음표(?)는 앞 요소와 0번 또는 1번 일치해요. 별표(*)처럼, 요소가 없어도 일치로 처리할 수 있어요.
# 대시가 없는 서버 이름을 포함해 모든 서버 이름에 True를 반환해요.
'SERVER01' -match '[A-Z]+-?\d\d'
{n, m} 수량자는 여러 방식으로 써서 일치 횟수를 더 세밀하게 다룰 수 있어요. 두 번째 요소 m과 쉼표(,)는 생략할 수 있어요.
| 수량자 | 설명 |
|---|---|
{n} |
정확히 n번 일치 |
{n,} |
최소 n번 일치 |
{n,m} |
n번 이상 m번 이하 일치 |
# 전화번호에 일치하면 True를 반환해요.
'111-222-3333' -match '\d{3}-\d{3}-\d{4}'
앵커 (Anchor)
앵커는 입력 문자열 안에서의 위치에 따라 일치 여부를 결정하게 해줘요. 흔히 쓰는 앵커는 두 가지, ^와 $예요. 캐럿 ^는 문자열의 시작을, $는 문자열의 끝을 일치시켜요. 앵커를 쓰면 원하는 위치에서 텍스트를 맞추는 동시에 불필요한 문자는 걸러낼 수 있죠.
# 'fish'라는 문자열이 그 줄에 홀로 있기를 기대하는 패턴이에요. 이건 FALSE예요.
'fishing' -match '^fish$'
앵커($)가 들어간 정규식을 정의할 때는 정규식을 작은따옴표(')로 감싸는 게 좋아요. 큰따옴표(")로 감싸면 PowerShell이 그 문자열을 확장 가능한 변수 표현식으로 해석하거든요.
PowerShell에서 앵커를 쓸 때는 Singleline과 Multiline 정규식 옵션의 차이를 알아둘 필요가 있어요.
- Multiline: 이 모드는
^와$가 입력 문자열의 처음과 끝이 아니라 줄마다 그 줄의 처음과 끝에 일치하도록 만들어요. - Singleline: 이 모드는 입력 문자열을 하나의 단일 줄로 취급해요. 그래서
.이 줄바꿈\n을 제외한 문자에만 일치했던 것과 달리, 모든 문자(줄바꿈 포함)와 일치하도록 만들어요.
이 옵션들과 사용법을 더 알고 싶다면 Regular Expression Language - Quick Reference를 참고해요.
문자 이스케이프
백슬래시(\)는 정규식 엔진이 문자를 해석하지 않도록 이스케이프할 때 써요. 다음 문자들은 예약되어 있어요: [().\^$|?*+{.
이 문자들을 입력 문자열에서 일치시키려면 패턴에서 이스케이프해 줘야 해요.
# 소수점을 백슬래시로 이스케이프해서, 소수점 이하 2자리 이상인 숫자를 일치시켜요.
'3.141' -match '3\.\d{2,}'
regex 클래스에는 텍스트를 대신 이스케이프해 주는 정적 메서드도 있어요.
[regex]::Escape( '3.\d{2,}' )
3\.\\d\{2,}
이 메서드는 문자 클래스에 들어 있는 기존 백슬래시를 포함해, 예약된 정규식 문자를 전부 이스케이프해요. 그러니 꼭 이스케이프가 필요한 부분에만 적용하는 게 좋아요.
그 밖의 문자 이스케이프
특수한 문자 유형을 일치시키기 위해 쓸 수 있는 예약 문자 이스케이프도 있어요. 자주 쓰는 몇 가지를 보면:
| 문자 이스케이프 | 설명 |
|---|---|
\t |
탭 |
\n |
줄바꿈 |
\r |
캐리지 리턴 |
그룹, 캡처, 대체(substitution)
그룹핑 구조는 입력 문자열을 캡처하거나 무시할 수 있는 하위 문자열로 나눠줘요. 이렇게 나눈 하위 문자열을 **하위 표현식(subexpression)**이라고 불러요. 기본적으로 하위 표현식은 번호가 매겨진 그룹으로 캡처되지만, 이름을 붙일 수도 있어요.
그룹핑 구조는 괄호로 감싼 정규식이에요. 괄호 안 정규식이 일치시킨 텍스트는 전부 캡처돼요. 아래 예시는 입력 텍스트를 두 개의 캡처 그룹으로 나눠요.
'The last logged on user was CONTOSO\jsmith' -match '(.+was )(.+)'
True
캡처된 텍스트를 가져오려면 $Matches Hashtable 자동 변수를 써요. 전체 일치를 나타내는 텍스트는 키 0에 저장돼요. 여기서 중요한 점은, $Matches 해시테이블에는 일치하는 패턴의 첫 번째 발생만 들어 있다는 거예요.
$Matches . 0
The last logged on user was CONTOSO\jsmith
캡처는 왼쪽에서 오른쪽으로 커지는 숫자(Integer) 키에 저장돼요. 캡처 1에는 사용자 이름 앞의 텍스트 전체가, 캡처 2에는 사용자 이름만 들어가요.
$Matches
Name Value ---- ----- 2 CONTOSO\jsmith 1 The last logged on user was 0 The last logged on user was CONTOSO\jsmith
키 0은 Integer라는 점을 기억해 두세요. 여기 저장된 값은 Hashtable의 어떤 메서드로든 접근할 수 있어요.
PS> 'Good Dog' -match 'Dog' True PS> $Matches[0] Dog PS> $Matches.Item(0) Dog PS> $Matches.0 Dog
명명된 캡처 (Named Captures)
기본적으로 캡처는 왼쪽에서 오른쪽으로 오름차순 숫자 순서로 저장돼요. 여기에 캡처 그룹에 이름을 붙일 수도 있어요. 그 이름은 $Matches Hashtable 자동 변수의 키가 돼요.
캡처 그룹 안에서 ?<키이름>을 쓰면 캡처된 데이터를 이름이 붙은 키 아래에 저장해요.
PS> $string = 'The last logged on user was CONTOSO\jsmith' PS> $string -match 'was (?<domain>.+)\\(?<user>.+)' True PS> $Matches Name Value ---- ----- domain CONTOSO user jsmith 0 was CONTOSO\jsmith PS> $Matches.domain CONTOSO PS> $Matches.user jsmith
아래 예시는 Windows 보안 로그에서 가장 최근 로그 항목을 가져와요. 주어진 정규식이 메시지에서 사용자 이름과 도메인을 뽑아내고, 이를 키 N(이름)과 D(도메인) 아래에 저장해요.
$log = ( Get-WinEvent -LogName Security -MaxEvents 1 ).Message
$r = '(?s).*Account Name:\s*(?<N>.*).*Account Domain:\s*(?<D>[A-Z,0-9]*)'
$log -match $r
True
$Matches
Name Value ---- ----- D CONTOSO N jsmith 0 A process has exited...
더 자세한 내용은 Grouping Constructs in Regular Expressions를 참고해요.
정규식 대체(substitution)
-replace 연산자와 정규식을 함께 쓰면, 캡처한 텍스트를 이용해 텍스트를 동적으로 바꿀 수 있어요.
<입력> -replace <원본>, <대체>
<입력>: 검색할 문자열<원본>: 입력 문자열을 검색하는 데 쓰는 정규식<대체>: 일치 항목을 대체할 정규식 대체 표현식
<원본>과 <대체> 피연산자는 문자 이스케이프나 대체 표현식 같은 정규식 엔진의 규칙을 따르게 돼요. 대체 패턴은 하나 이상의 대체와 글자 그대로의 문자로 구성할 수 있어요.
캡처 그룹은 <대체> 문자열 안에서 그룹 식별자 앞에 $를 붙여 참조할 수 있어요. 캡처 그룹을 참조하는 방법은 숫자와 이름 두 가지예요.
- 숫자로 – 캡처 그룹은 왼쪽에서 오른쪽으로 번호가 매겨져요.
'John D. Smith' -replace '(\w+) (\w+)\. (\w+)' , '[email protected]'
[email protected]
- 이름으로 – 캡처 그룹은 이름으로도 참조할 수 있어요.
'CONTOSO\Administrator' -replace '\w+\\(?<user>\w+)' , 'FABRIKAM\${user}'
FABRIKAM\Administrator
$& 표현식은 일치한 텍스트 전체를 나타내요.
'Gobble' -replace 'Gobble' , '$& $&'
Gobble Gobble
$ 문자는 문자열 확장에 쓰이기 때문에, 대체에는 리터럴 문자열을 쓰거나 큰따옴표를 쓸 때 $ 문자를 이스케이프해야 해요.
'Hello World' -replace '(\w+) \w+' , '$1 Universe'
"Hello World" -replace "(\w+) \w+" , "`$1 Universe"
Hello Universe Hello Universe
추가로, $를 글자 그대로 쓰고 싶다면 일반 이스케이프 대신 $$를 써요. 큰따옴표를 쓸 때는 잘못된 대체를 피하려고 모든 $를 여전히 이스케이프해야 해요.
'5.72' -replace '(.+)' , '$$$1'
"5.72" -replace "(.+)" , "`$`$`$1"
$5.72 $5.72
대체 표현식에 대한 자세한 내용은 Substitutions in Regular Expressions를 참고해요.
정규식은 매우 복잡해져서 읽기 어려울 때가 있어요. 그럴 땐 주석을 달아 이해하기 쉽게 만들 수 있어요. 정규식에서 허용되는 주석은 두 가지예요.
- 인라인 주석 (
(?#)) - 줄 끝 주석 (
#)
자세한 내용은 about_Comments의 Regular expression comments 섹션을 참고해요.