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가지의 방법으로 만들 수 있습니다.

 

  1. 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

코틀린에서 정규 표현식 사용하기

정규표현식의 개념과 코틀린에서 정규표현식 사용해보기

반응형