정규 표현식(정규식) 은 특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어로 문자열 검색, 치환에 많이 사용된다.
대표적인 예시로, 전화번호, 이메일 등의 형식으로 입력을 받았을 때 그 형식이 맞는지를 확인하는 방식으로 자주 사용된다.
Swift도 역시 정규식을 사용할 수 있다.
정규식 문법

정규식 문법은 외우고 있으면 신기할정도다. 자주 쓰지 않고 필요할때만 문법을 찾아서 조합해 사용하다보니 자주 까먹게된다.
자주 사용하는 것들은 예제로 알아보고
자세한 문법은 https://regexr.com 에서 RegEx Reference 를 참고하면서 만들면 된다.
테스트도 가능하기 때문에 유용한 사이트다
정규식 문법은 드림코딩 유튜브에서 잘 설명되어있으니 참고하기.
패턴 | 설명 |
---|---|
. |
임의의 한 문자와 일치합니다. (줄바꿈 문자를 제외) |
^ |
문자열의 시작을 나타냅니다. |
$ |
문자열의 끝을 나타냅니다. |
* |
앞의 요소가 0번 이상 반복됨을 의미합니다. |
+ |
앞의 요소가 1번 이상 반복됨을 의미합니다. |
? |
앞의 요소가 0번 또는 1번 나타남을 의미합니다. |
{n} |
정확히 n번 반복됨을 의미합니다. |
{n,} |
최소 n번 이상 반복됨을 의미합니다. |
{n,m} |
최소 n번 이상, 최대 m번 이하로 반복됨을 의미합니다. |
[abc] |
a, b, 또는 c 문자 중 하나와 일치합니다. |
[^abc] |
a, b, c 문자를 제외한 문자와 일치합니다. |
[a-z] |
소문자 a부터 z까지의 범위 내 문자와 일치합니다. |
[A-Z] |
대문자 A부터 Z까지의 범위 내 문자와 일치합니다. |
[0-9] |
숫자 0부터 9까지의 범위 내 문자와 일치합니다. |
\ |
메타문자를 이스케이프하거나 특수 시퀀스를 나타낼 때 사용합니다. |
\d |
숫자 문자와 일치합니다. (동일: [0-9] ) |
\D |
숫자 문자가 아닌 것과 일치합니다. (동일: [^0-9] ) |
\w |
단어 문자(알파벳, 숫자, 언더스코어)와 일치합니다. (동일: [a-zA-Z0-9_] ) |
\W |
단어 문자가 아닌 것과 일치합니다. (동일: [^a-zA-Z0-9_] ) |
\s |
공백 문자와 일치합니다. (스페이스, 탭, 줄바꿈 포함) |
\S |
공백 문자가 아닌 것과 일치합니다. |
(abc) |
abc 를 하나의 그룹으로 묶습니다. |
(?:abc) |
비캡처 그룹: 그룹으로 묶되 캡처하지 않습니다. |
\\\\| |
OR 연산자로, A|B는 A 또는 B와 일치합니다. |
(?=...) |
긍정형 전방 탐색: 특정 패턴이 뒤따르는 경우 일치합니다. |
(?!...) |
부정형 전방 탐색: 특정 패턴이 뒤따르지 않는 경우 일치합니다. |
(?<=...) |
긍정형 후방 탐색: 특정 패턴이 앞에 있는 경우 일치합니다. |
(?<!...) |
부정형 후방 탐색: 특정 패턴이 앞에 없는 경우 일치합니다. |
\b |
단어 경계를 나타냅니다. |
\B |
단어 경계가 아닌 곳과 일치합니다. |
공부를 하고나면 정규식 문법 퀴즈를 풀어보는걸 추천한다.
https://regexone.com
Regex
Foundation에 내장된 API로 Swift 5.7, iOS 16 이상부터 사용 가능하다.
생성 방법
먼저 사용할 정규표현식을 생성하는 방법이다.
Regex를 사용해 정규식을 생성하는 방법은 3가지가 있다.
let regex1 = try? Regex(#"Hi, WWDC\d{2}!"#)
let regex2 = /Hi WWDC\d{2}!/
import RegexBuilder
let regex3 = Regex {
"Hi, WWDC"
Repeat(.digit, count: 2) /* /\d{2}/ //이걸로도 대체가능 */
"!"
}
첫번째 방법, Regex.init에 정규식 작성
두번째 방법, 역슬래시로 정규식을 감싸면 컴파일러가 알아서 첫번째 방식과 같이 만들어준다.
세번째 방법, Regex Builder DSL (Domain Specific Language)를 사용하기.
Regex Builder는 정규표현식을 쉽게 작성하기 위해서 Regex body에 사용할 수 있는 간편한 메서드들을 정의해준다.
Regex Literal을 섞어서 사용해도 무관하다.
let regex3 = Regex {
"Hi, WWDC"
Capture {
Repeat(.digit, count: 2)
}
"!"
}
Capture 기능도 있다 Regex에서 () 을 쓰는 것 처럼 그룹을 만들어주고, 이 표현식으로 매칭했을 경우 캡처한것은 매칭한것과 별개로 캡처해서 반환해준다.
RegexBuilder | Apple Developer Documentation
Use an expressive domain-specific language to build regular expressions, for operations like searching and replacing in text.
developer.apple.com
사용
String에 검사를 위한 내장함수가 존재한다.
let input1 = "Hi, WWDC22! Hi, WWDC11! Good Morning"
let input2 = "Hi, WWDC22!"
let input3 = "aHi, WWDC22!"
print(input1.firstMatch(of: regex2)?.output) // Optional("Hi WWDC22!")
print(input3.firstMatch(of: regex2)?.output) // Optional("Hi WWDC22!")
print(input1.prefixMatch(of: regex2)?.output) // Optional("Hi WWDC22!")
print(input3.prefixMatch(of: regex2)?.output) // nil
print(input1.matches(of: regex2).map{$0.output}) // ["Hi, WWDC22!", "Hi, WWDC11!"]
print(input1.wholeMatch(of: regex2)?.output) // nil
print(input2.wholeMatch(of: regex2)?.output) // Optional("Hi WWDC22!")
firstMatch
- regex에 첫 매칭되는 문자열 반환prefixMatch
- 첫 글자부터 regex에 매칭되는지 확인 후 반환, 아니라면 nilwholeMatch
- regex와 string이 완벽히 일치하면 그대로 return, 아니라면 nilmatches
- regex에 매칭되는 문자열 전부 반환(sequence)
Optional 반환이 기본이기에 제대로 사용하기 위해서는 옵셔널 바인딩을 사용해서 구현하면 된다.
추가로 input1.contains(regex2)
도 가능하다! contains 메서드가 regex도 지원한다 ^^
Regex 타입에 따른 반환값의 차이
진짜 뜬금없지만, 간단히 매칭된 것만 출력하려고 보니 반환값이 달라서 추가작업이 필요한 것들이 있었다.
그냥 쓰면 되겠지만 이상하게 신경쓰여서 알아본것을 추가로 정리한다.
진짜 미세지식 이니까 시간 남으면 읽어보세요...
print(input1.firstMatch(of: regex1)?.output.flatMap{$0.substring})
print(input1.firstMatch(of: regex2)?.output)
print(input1.firstMatch(of: regex3)?.output)
print(input1.prefixMatch(of: regex1)?.output.flatMap{$0.substring})
print(input1.prefixMatch(of: regex2)?.output)
print(input1.prefixMatch(of: regex3)?.output)
print(input2.wholeMatch(of: regex1)?.output.flatMap{$0.substring})
print(input2.wholeMatch(of: regex2)?.output)
print(input2.wholeMatch(of: regex3)?.output)
print(input1.matches(of: regex1).flatMap{$0.output.flatMap{$0.substring}})
print(input1.matches(of: regex2).map{$0.output})
print(input1.matches(of: regex3).map{$0.output})
간단하게 출력만 해봤는데, 생각한거처럼 매칭된 문자열이 바로 나오는게 아니라 당황했다.regex1
을 사용해서 매칭해본 것들은 2depth나 들어가서 값을 찾아올 수 있었다.
자세히 찾아보니 regex1
방식처럼 생성할 경우 타입은 Regex<AnyRegexOutput>
이다.
나머지 2와 3은 각각 Regex<Substring>
Regex<Regex<Substring>.RegexOutput>
으로 타입이 전부 다르긴 하지만, 2와 3은 Regex<Substring>
을 가지고 있는것을 알 수 있다.
AnyRegexOutput
의 경우 정규식이 동적으로 생성되었을 때 사용되며, 매칭된 부분에 값 외에도 메타정보 (범위, 캡쳐그룹 등)를 가지고 있다.Regex<Substring>
은 정적으로 생성되었을 때 사용되고, 매칭된 부분의 값만을 가지고 있다.
하지만... Regex<Substring>
타입으로 생성해서 사용해도 input1.firstMatch(of: regex2).range
이렇게 범위에 접근할 수 있는데?
그건 메서드가 반환해준 값일 뿐... input1.firstMatch(of: regex2)?.output
을 확인하면 값만 접근 가능하단걸 확인할 수 있다.
NSRegularExpression
Regex가 Foundation API로 나오기 전 사용되었던 정규식 처리 API. iOS7+
let testString = "01712345678"
if let regex = try? NSRegularExpression(pattern: "^01[0-1|7][0-9]{7,8}$") {
let range = NSRange(location: 0, length: testString.utf16.count)
let matches = regex.firstMatch(in: testString, options: [], range: range)
let isMatch = matches?.range == range
print(isMatch) // true (완전 일치)
}
firstMatch(in:options:range:)
- 첫 번째로 매칭된것을 반환.
만약 정규식에 \d
와 같은 구문이 들어가야하면 \\d
처럼 역슬래시를 하나 더 붙여줘야한다.
매칭을 위한 메서드는 Regex 방식에서 사용했던 String에서 제공하는 내장함수랑 비슷하다.
matches(in:options:range:)
- 매칭된 모든것을 반환
let input = "010-1234-5678, 011-9876-5432, 017-5555-4444"
let matches = regex.matches(in: input, options: [], range: NSRange(input.startIndex..., in: input))
for match in matches {
print("매칭된 값: \(input[Range(match.range, in: input)!])")
}
rangeOfFirstMatch(in:options:range:)
- 첫 번째로 매칭된 문자열의 범위를 반환
let range = regex.rangeOfFirstMatch(in: input, options: [], range: NSRange(input.startIndex..., in: input))
if range.location != NSNotFound {
print("매칭된 범위: \(range)") // 예: (5, 13) → 5번째 위치부터 13글자 길이
}
enumerateMatches(in:options:range:using)
-
regex.enumerateMatches(in: input, options: [], range: NSRange(input.startIndex..., in: input)) { match, _, _ in
if let match = match {
print("매칭된 값: \(input[Range(match.range, in: input)!])")
}
}
replacementString(for:in:offset:template:)
- 첫 번째 매칭된 문자열을 템플릿(파라미터로 넣은 템플릿)으로 변환
let template = "XXX-XXXX-XXXX"
if let match = regex.firstMatch(in: input, options: [], range: NSRange(input.startIndex..., in: input)) {
let replaced = regex.replacementString(for: match, in: input, offset: 0, template: template)
print("변환된 값: \(replaced)") // 출력: XXX-XXXX-XXXX
}
stringByReplacingMatches(in:options:range:withTemplate
- 모든 매칭된 문자열을 템플릿으로 변환
let masked = regex.stringByReplacingMatches(in: input, options: [], range: NSRange(input.startIndex..., in: input), withTemplate: "XXX-XXXX-XXXX")
print(masked)
// 출력: XXX-XXXX-XXXX, XXX-XXXX-XXXX, XXX-XXXX-XXXX
NSPredicate
Regex가 Foundation API로 나오기 전 사용되었던 정규식 처리 API. iOS3+
CoreData의 특정 데이터를 검색할때 쓰지만, 이렇게 정규식을 처리하는 용도로 사용할 수있다.
let regex = "^01[0-1|7][0-9]{7,8}$"
let phonePredicate = NSPredicate(format: "SELF MATCHES %@", regex)
let isValid = phonePredicate.evaluate(with: "01712345678")
print(isValid) //true
만약 정규식에 \d
와 같은 구문이 들어가야하면 \\d
처럼 역슬래시를 하나 더 붙여줘야한다.