ABOUT ME

-

  • 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

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

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

    반응형

    댓글

Designed by Me.