PowerShell의 정규 표현식은 .NET의 것이다. 문법은 .NET 문서를 따르고, PowerShell은 그것을 쓰기 편한 연산자와 명령으로 감싸 준다.
-match 와 $Matches
-match는 맞으면 True를 돌려주고, 맞은 내용을 자동 변수 $Matches에 넣는다.
if ('2026-10-04' -match '(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})') {
$Matches.y
$Matches
}
2026
Name Value
---- -----
d 04
m 10
y 2026
0 2026-10-04
0은 맞은 전체다. 괄호에 (?<이름>...)처럼 이름을 붙이면 그 이름으로 꺼낼 수 있다. 번호로 세는 것보다 읽기 쉽고, 패턴을 고쳐도 번호가 밀리지 않는다.
알아 둘 것 둘.
$Matches는 마지막으로 성공한 비교의 결과다. 실패하면 바뀌지 않으므로, 반드시if안에서 쓴다.- 왼쪽이 배열이면
-match는 맞는 원소들을 돌려주고$Matches는 채우지 않는다.
-match는 대소문자를 가리지 않는다. 가리려면 -cmatch를 쓴다.
-replace: 바꾸기
바꿀 문자열에서 $1, ${이름}으로 그룹을 가리킨다. PowerShell이 변수로 풀지 않게 작은따옴표를 쓴다.
'2026-10-04' -replace '(\d{4})-(\d{2})-(\d{2})', '$3/$2/$1'
# 04/10/2026
바꿀 내용을 계산해야 하면 스크립트 블록을 준다. $_는 맞은 부분이다. 2010년에는 [regex]::Replace()에 대리자를 넘겨야 했던 일이다.
'hello world' -replace '\b\w', { $_.Value.ToUpper() }
# Hello World
switch -Regex
여러 패턴 가운데 맞는 것을 고를 때는 switch가 깔끔하다. 배열을 주면 원소마다 돈다.
switch -Regex ('error: disk full', 'info: ok') {
'^error' { "E: $_" }
'^info' { "I: $_" }
}
E: error: disk full
I: info: ok
Select-String: 파일에서 찾기
grep에 해당한다. 돌려주는 것은 글자가 아니라 파일 이름, 줄 번호, 줄 내용, 맞은 그룹을 담은 개체다.
Select-String -Path ./*.log -Pattern 'timeout after (\d+)ms' |
ForEach-Object {
[pscustomobject]@{
File = $_.Filename
Line = $_.LineNumber
Ms = [int]$_.Matches[0].Groups[1].Value
}
} |
Sort-Object Ms -Descending
하위 폴더까지 찾으려면 Get-ChildItem -Recurse의 결과를 넘긴다.
Get-ChildItem -Recurse -Filter *.ps1 | Select-String 'Invoke-WebRequest'
예제: 글자로 된 목록을 개체로
2010년에 FTP 서버가 돌려주는 파일 목록을 정규식으로 풀어 개체로 바꾸는 글을 썼다. FTP는 이제 쓸 일이 드물지만, 글자로만 결과를 주는 도구를 다루는 방법으로는 지금도 그대로 통한다. 같은 일을 지금의 문법으로 쓰면 이렇다.
$listing = @'
drwxrwxrwx 10 1000 1000 4096 Jul 16 09:53 Storage
-rw-r--r-- 1 1000 1000 179 Jun 16 21:19 examples.desktop
drwxr-xr-x 2 1000 1000 4096 Jun 16 21:29 문서
'@
$pattern = '^(?<type>[-dl])(?<mode>[-rwxs]{9})\s+\d+\s+(?<owner>\S+)\s+(?<group>\S+)\s+(?<size>\d+)\s+(?<date>\w{3}\s+\d+\s+[\d:]+)\s+(?<name>.+)$'
$listing -split '\r?\n' | ForEach-Object {
if ($_ -match $pattern) {
[pscustomobject]@{
Name = $Matches.name
Type = $Matches.type -eq 'd' ? 'Directory' : 'File'
Size = [long]$Matches.size
Date = $Matches.date
}
}
} | Sort-Object Size -Descending
Name Type Size Date
---- ---- ---- ----
Storage Directory 4096 Jul 16 09:53
문서 Directory 4096 Jun 16 21:29
examples.desktop File 179 Jun 16 21:19
옛 코드와 견주면 세 가지가 달라졌다. 임시 파일에 적었다가 다시 읽던 것을 -split으로 바로 나눈다. New-Object PSObject는 [pscustomobject]가 됐다. if/else는 삼항 연산자가 됐다.
한 가지 조언. 도구가 JSON이나 CSV로 출력하는 옵션을 주면 정규식보다 그것을 먼저 쓴다. 정규식으로 화면용 출력을 푸는 것은 다른 길이 없을 때의 방법이다.
직접 .NET을 쓸 때
같은 패턴을 수만 번 쓰거나 맞는 것을 전부 찾아야 하면 [regex]를 직접 쓴다.
$text = 'a=1, b=22, c=333'
[regex]::Matches($text, '(\w)=(\d+)') | ForEach-Object {
"$($_.Groups[1].Value) → $($_.Groups[2].Value)"
}
다음 글: 배열과 해시 테이블