-
Kotlin) 정규 표현식 정리Kotlin 2021. 8. 17. 00:10
정규 표현식
- 정규 표현식 또는 정규식은 특정한 규칙을 가진 문자열의 집합을 표현하기 위해 사용하는 형식 언어.
- 어떤 문자열에서 특정한 조건의 문자열을 찾고 싶을 때, 그 조건이 복잡한 경우 유용. 예를 들어 비밀번호 설정(최소 8자리에 숫자, 문자, 특수문자 각각 1개 이상 포함 등)
정규 표현식 문법
- ^ : 문자열의 시작을 의미.
- $ : 문자열의 끝을 의미.
- . : 문자 한 개를 의미. '.'이 위치한 곳에 어떤 문자든지 1개의 문자가 들어감.
- [ ] : 대괄호에 있는 문자 중 한 개를 의미. [abc]는 a, b, c 중 하나를 선택.
- [^] : not의 의미로, 대괄호에서 쓴다면 [^abc] : a, b, c 제외하고 나머지를 의미.
- | : or을 의미. a|b : a 또는 b.
- () : 공통되는 부분을 묶을 때, 서브 패턴을 지정할 때 사용. abc|abd -> ab(c|d)로 바꿀 수 있음.
- ? : 문자가 0회 또는 1회 등장. a? b는 a가 나올 수도, 없을 수도 있음. ab, b.
- * : 문자가 0회 이상 등장. a*b : b, ab, aaab, aaab..
- + : 문자가 1회 이상 등장. a+b : ab, aab, aaab..
- {n} : 문자가 n개 나옴. a {2} b : aab
- {n,} : 문자가 n개 이상 나옴. a {2,} b : aab, aaab, aaaab..
- {n, m} : 문자가 n개 이상 m개 이하로 나옴. a {1,3 } b : ab, aab, aaab
- \s : 공백 제거
- \t : 탭
- \d : 숫자, [0-9]와 동일
- \b : 단어의 경계, 문자 사이의 공백
- \w : 알파벳이나 숫자, [a-zA-Z0-9_]와 동일
- 위의 \s, \t, \d, \b, \w는 대문자로 바꾸면 반대 의미가 됩니다.
더 많은 정규식 문법
정규 표현식 사용해보기
- 코틀린은 Regex 클래스의 객체를 이용해서 정규 표현식을 처리합니다. 정규 표현식은 다음과 같은 4가지의 방법으로 만들 수 있습니다.
- Regex 클래스의 생성자 이용
val regex = Regex("[0-9|a-z]")
2. String 클래스의 확장 함수 toRegex() 이용
val regex ="[0-9|a-z]".toRegex()
3. Regex 클래스의 Compnion Object 함수 fromLiteral() 이용
val regex = Regex.fromLiteral("[0-9|a-z]")
- 1, 2번과 달리 3번 방법은 입력값을 문자열 리터럴로 처리합니다. 이스케이프 문자를 모두 일반 문자로 처리하므로, 즉 "[0-9|a-z]"와 정확히 일치한 문자열만 찾습니다.
4. 삼중 따옴표를 이용한 정규식 처리
- \(역슬래시)는 이스케이프 문자이므로 앞에 \(역슬래시)를 추가하여 해당 역슬래시는 일반 문자임을 표시해야 합니다.
- 복잡한 정규식을 작성하다 보면 역슬래시가 문자열에 추가되게 되는데, 이때 삼중 따옴표를 이용해서 깔끔하게 표현할 수 있습니다.
- 삼중 따옴표 안에서는 이스케이프 문자를 따로 처리할 필요가 없습니다.
val regex = """\([A-Z]\w+\)""".toRegex()
matchEntire()
- 입력값이 정규식과 일치하는지 확인하고 싶을 때 사용하는 함수입니다.
val matchResult : MatchResult? = regex.matchEntire("abcd")
find()
- 정규식과 일치하는 첫 번째 요소를 찾고 싶을 때 사용하는 함수입니다.
- value 프로퍼티는 정규식을 이용하여 찾은 결과를 문자열로 담고 있습니다.
- range 프로퍼티는 입력값에서 value2의 값이 위치하는 첫 번째 인덱스와 마지막 인덱스를 IntRange 객체로 담고 있습니다.
val regex = "[a-z]".toRegex() val matchResult : MatchResult? = regex.find("abcd") val value : String = matchResult!!.value println(value) val value2 : IntRange = matchResult!!.range println(value2)
- MatchResult 클래스의 groups 프로퍼티는 MatchGroupCollection의 객체이므로 get 메서드를 통해 MatchGroup 객체를 반환받을 수 있고, 이 MatchGroup은 value와 range 두 개의 프로퍼티를 가집니다.
- 각 결과의 인덱스는 다음과 같이 구할 수 있습니다.
val groupValueIndex : IntRange = matchResult!!.groups[0]!!.range
- 또한, next() 메서드를 이용하여 다음 결과를 조회할 수 있습니다.
val value1: String? = matchResult?.value matchResult = matchResult?.next() val value2: String? = matchResult?.value println("$value1 $value2")
- a([bc]+) d? 는 첫 번째 그룹 a [bc]+d? 와 두 번째 그룹 [bc]+로 나눌 수 있습니다.
val regex = """a([bc]+)d?""".toRegex() val matchResult = regex.find("abcd") val groupValues: List<String> = matchResult!!.groupValues for (value in groupValues) { println(value) }
findAll()
- 정규식과 일치하는 모든 요소를 찾고 싶을 때 사용하는 함수입니다.
val matchResult: Sequence<MatchResult> = regex.findAll("abcd")
- MatchResult 클래스의 객체를 반환하는 matchEntire(), find() 메서드는 결괏값이 없을 때 null을 반환합니다.
- Sequence <MatchResult>를 반환하는 findAll() 메서드는 결괏값이 없을 때 해당 시퀀스의 count는 0입니다.
자바와의 호환
- 자바와의 호환을 위해 Regex 클래스는 toPattern() 메서드를 제공합니다.
val regex = """\W+""".toRegex() val pattern: Pattern = regex.toPattern()
정규 표현식 예제
1. 인터넷 주소
- 기본적으로 scheme://host로 되어있는 url을 예제로 보겠습니다.
fun main() { val path = "https://naver.com" val regex = "https://(.+)".toRegex() isMatch(path, regex) } fun isMatch(path: String, regex: Regex) { if (path.matches(regex)) println("match") else println("not match") }
- 또는 https로 시작하는 주소 말고 여러 프로토콜을 사용하는 경우.
fun main() { val path1 = "https://naver.com" val path2 = "http://daum.net" val path3 = "ftp://example.com" val path4 = "telnet://abc.com" val regex = "(https|http|ftp|telnet)://(.+)".toRegex() isMatch(path1, regex) isMatch(path2, regex) isMatch(path3, regex) isMatch(path4, regex) }
- url의 scheme://host/path/qurey로 되어있는 경우 정규식을 사용해보겠습니다.
fun main() { val path = "https://naver.com/path/data?key=value" val regex = "([a-z]+)://([a-z.]+)/(.*)\\?(.*)".toRegex() isMatch(path, regex).also { println(it) val res = regex.matchEntire(path) if (res != null) { for (value in res.groupValues) { println(value) } val (s, h, p, q) = res.destructured println("scheme: $s, host: $h, path: $p, query: $q") } } }
- 아예 url 내에 있는 스키마, 호스트, 포트, 경로, 쿼리 문자열 등을 모두 추출하여 규칙을 뽑아낸 정규식은 다음과 같습니다.
fun main() { val path = "https://naver.com" val regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]".toRegex() isMatch(path, regex) } fun isMatch(path: String, regex: Regex) { if (path.matches(regex)) println("match") else println("not match") }
2. 파일 구조 확인
- 디렉터리 주소에서 파일 이름과 파일 확장자를 가져오는 경우.
- destructured는 객체의 데이터를 변수들에 대입하는 것입니다.
fun main() { val path = "folder1/folder2/folder3/filename.java" val regex = "(.+)/(.+)\\.(.+)".toRegex() isMatch(path, regex).also { println(it) val res = regex.matchEntire(path) if (res != null) { val (p, n, e) = res.destructured println("$p, filename: $n, extension: $e") } } }
- 정규식을 사용하지 않는다면 아래와 같은 방법이 있을 수 있겠습니다. 문자열이 길어지고 복잡해지면 곤란해지겠죠?
fun main() { val path = "folder1/folder2/folder3/filename.java" val filename = path.substringBeforeLast(".").substringAfterLast("/") val extension = path.substringAfterLast(".") println("filename: $filename, extension: $extension") }
3. IP 주소의 뒷자리 바꾸기
- 맨 뒤를 특정 숫자로 바꾸거나 모든 숫자를 특정 문자로 바꾸는 예제입니다.
- "\\d+"는 숫자를 1개 이상 사용한 것, "$"는 문자열의 맨 끝을 의미.
fun main() { val path = "192.168.0.125" val regex1 = path.replace("\\.\\d+$".toRegex(), ".111") println(regex1) val regex2 = path.replace("\\d+".toRegex(), "a") println(regex2) }
Preference
반응형'Kotlin' 카테고리의 다른 글
Kotlin) Coroutine 공식 가이드 번역 06 - Channels (0) 2021.01.25 Kotlin) Coroutine 공식 가이드 번역 05 - Asynchronous Flow(2/2) (0) 2021.01.20 Kotlin) Coroutine 공식 가이드 번역 05 - Asynchronous Flow(1/2) (0) 2021.01.20 Kotlin) Coroutine 공식 가이드 번역 04 - Coroutine Context and Dispatchers (0) 2021.01.15 Kotlin) Coroutine 공식 가이드 번역 03 - Composing Suspending Functions (0) 2021.01.15