정규 표현식

하루공부·2024년 1월 24일
0

C++

목록 보기
23/25
post-thumbnail

C++ 아이콘 제작자: Darius Dan - Flaticon


정규 표현식

  • C++ 11 부터 표준에 포함된 정규 표현식(regular expression) 라이브러리
  • 정규 표현식은 문자열에서 패턴을 찾는데 사용하는데 다음 같은 경우에 매우 유용하게 사용됩니다
    • 주어진 문자열이 주어진 규칙에 맞는지 확인할 때
    • 주어진 문자열에서 원하는 패턴의 문자열을 검색할 때
    • 주어진 문자열에서 원하는 패턴의 문자열로 치환할 때

  • 우선 정규 표현식에서 쓰는 표현의 의미는 아래와 같다.
^x // '^'은 문자열의 시작을 표현하며, x문자로 시작됨을 의미
x$ // '$'은 문자열의 종료를 표현하며, x문자로 종료됨을 의미
.x // '.'은 개행문자 \n을 제외한 다른 모든 문자를 의미
x+ // '+'은 1회 이상 반복을 의미, x문자가 1번 이상 반복됨을 의미 ({1,}과 동일)
x* // '*'은 0회 이상 반복을 의미, x문자가 0번 이상 반복됨을 의미 ({0,}과 동일)
x? // '?'은 0 or 1개 문자 매칭 의미, x문자가 존재할 수도 있고 안할수도 있다는 의미 ({0,1}과 동일)
x|y // '|'은 or를 표현, x 또는 y가 나온다는 의미
(x) // '()'은 그룹을 표현, 괄호로 묶인 패턴을 의미 ((abc){3}와 같이 사용해서 abcabcabc를 검출하는데 쓰임)
x{n} // '{}'은 반복을 의미, x가 n번 반복됨을 의미
x{n,} // '{,}'은 반복을 의미, x가 n번 이상 반복됨을 의미
x{n,m} // '{}'은 반복을 의미, x가 n번 이상 m번 이하로 반복됨을 의미
[xy] // '[]'은 x또는 y를 찾는다는 의미, [a-z0-9]이면 알파벳 소문자 또는 숫자를 찾는다는 의미
[^xy] // '[^]'은 not을 의미, x 및 y 를 제외하고 찾는다는 의미
[a-z] // '[-]'은 a ~ z 를 찾는다는 의미

\d // '\d'은 digit으로 숫자를 의미
\D // '\D'은 not digit으로 숫자를 제외하고 나머지 다른 문자를 의미
\s // '\s'은 space로 공백문자를 의미
\S // '\S'은 not space로 공백문자를 제외한 나머지 다른 문자를 의미
\t // '\t'은 tap을 의미
\w // '\w'은 알파벳 대문자,소문자와 숫자를 의미, [A-Za-z0-9]을 의미
\W // '\W'은 not \w, 즉 \w를 제외한 특수문자를 의미

(?:) // 캡쳐하지 않는 그룹 생성



문자열이 원하는 패턴과 일치하는가??

  • 예제로 알아보자 ( 예를 들어 서버 관리를 하는데 시간 마다 로그 파일을 생성한다. )

    예시로 해당 로그 파일은 db-(시간)-log.txt 형태로 생성된다.
    여러가지 파일이 있는 폴더에서 위의 파일만 읽어낼 수 있을까?
    위의 내용을 참고하여 표현하면 정규 표현식은 db-\d*-log\.txt 이다.

    여기서 \d*는 임의의 갯수의 숫자를 의마하는 것이고 .txt는 앞에 \를 붙인 이유는 문자로 해석하는걸 방지하기 위해서다.

std::vector<std::string> file_names = { "db-123-log.txt", "db-124-log.txt", "not-db-log.txt", "db-12-log.txt", "db-12-log.jpg" };
std::regex re("db-\\d*-log\\.txt");
for (const auto& file_name : file_names)
{
	std::cout << file_name << ": " << std::boolalpha << std::regex_match(file_name, re) << '\n';
}
}

정규 표현식을 사용하기 위해 정규 표현식 객체를 정의해야 한다. std::regex re("db-\\d*-log\\.txt");

참고로 정규 표현식 문법의 종류와, 정규 표현식을 처리하는 엔진 역시 여러가지 종류가 있는데
추가적으로 생성자에 인자를 전달할 수 있는데 예를 들어 grep 정규 표현식을 쓰고 싶다면
std::regex re("db-\\d*-log\\.txt", std::regex::grep);처럼 전달하면 된다.
만약에 인자를 지정하지 않았다면 디폴트로 std::regex::ECMAScript 가 들어간다.
std::regex re("db-\\d*-log\\.txt", std::regex::grep | std::regex::icase); 처럼 추가할 수 있다.


std::regex_match(file_name, re) 전달된 문자열과 정규 표현식 객체를 비교하여
표현식과 완전히 매칭이 된다면 true를 리턴한다.




문자열의 원하는 부분을 뽑아서 사용 할 수 있는가??

  • regex_match함수로 패턴과 일치하는지 않하는지 확인만 하였다
  • 이번에는 일치하는 문자를 가져오는 방법이 있을까? ==> capture group(캡처 그룹)기능을 사용
    ==> 정규 표현식 객체에서 표현식에서 원하는 부분이 있으면 ()로 감싸면 된다.

    위 표현식을 가져오면 "db-\d*-log\.txt" 에서 가운데 숫자만을 가져오고 싶다고 하면
    "db-(\\d*)-log\\.txt" 이렇게 된다.

    이제 원하는 부분이 어디인지 표현을 했는데 이걸 어떻게 가져오냐??


  • std::smatch match함수는 매칭된 결과를 string으로 보관해준다.
    ==> 위에서 원하는 부분이 매칭된 결과를 여기에 보관하여 사용하면 되는거지
std::smatch match; // 매칭된 결과를 string 으로 보관
for (const auto& number : phone_numbers) {
    if (std::regex_match(number, match, re)) {
        for (size_t i = 0; i < match.size(); i++)
        {
            std::cout << "Match : " << match[i].str() << std::endl;
        }

smatch 객체를 선언하여 보관할 장소를 만들고,
regex_match함수에 비교할 문자열과 저장할 장소인 smatch객체를 넘긴후 정규 표현식을 전달한다.

그러면 전달한 정규 표현식과 일치하는 문자열을 smatch객체에 저장한다.

match[index].str() 이렇게 저장된 문자열을 사용할 수 있다.

저장된 문자열은 위에서 캡처 그룹을 사용한 문자열이 저장되어 있음




일부분의 문자열이 원하는 패턴과 일치하는가??

  • 문자열에서 원하는 패턴 검색하는 일은 regex_search를 사용하면 된다.
    첫 번째 인자는 검색할 문자열, 2번째는 패턴과 일치된 문자열을 저장할 match객체
    마지막 인자는 정규 표현식 객체
    ==> 매칭되는 문자열이 있다면 regex_search가 true를 리턴한다.

    문제는 while문에서 계속 찾았던 패턴을 계속 검색할 수 있다는 거다
    그래서 해당 문자열을 업데잍트해서 검색된 패턴 바로 뒤 부터 다시 검색하게 만들어야 한다.
    ==> match.suffix();

    해당 함수를 사용하면 std::sub_match 객체를 리턴한다.
    std::sub_match는 단순히 어떠한 문자열의 시작과 끝을 가리키는 반복자 2개를 가지고 있다.
    이 때 suffix의 경우 원 문자열에서 검색된 패턴 바로 뒤 부터 문자열의 끝까지 해당하는 sum_match를 리턴한다

    이 때 string으로 변환하는 캐스팅 연산자가 있어 원래 문자열에 그냥 대입하면
    기존 문자열에서 검색된 문자열이 제외된 문자열이 들어간다.
    ==> 그래서 검색된 문자열 다음 문자 부터 다시 검색을 시작한다.


std::regex_iterator

  • 정규 표현식의 반복자를 사용하면 좀 더 편리하게 검색할 수 있다.
    ==> 정규 표현식으로 매칭된 문자열들에 대한 반복자이다.

  • 어떻게 반복자를 선언하냐??

    std::sregex_iterator 우선 s가 붙어있는데 string을 사용하는 반복자이다.
    생성자 호출함으로 생성할 수 있다.
    1, 2번째 인자로 검색할 문자열의 시작과 끝을 전달하고(string의 begin(), end()함수 활용)
    마지막 인자는 정규 표현식 객체를 전달하면 된다.

auto start = std::sregex_iterator(html.begin(), html.end(), re);
==> sregex_iterator가 알아서 패턴에 맞는 문자열을 뽑아서 start객체에 저장한다.

regex_iterator 의 경우 처음 생성될 때와, ++ 연산자로 증감 될 때마다
regex_search 를 통해 초기에 주어진 문자열 범위 내에서 패턴에 맞는 문자열을 검색합니다.
==> 알아서 검색한 문자열을 걸러주는 거지.

그리하여 원하는 패턴들만 존재하는데 start->str() 이렇게 역참조로 접근이 가능하다.




원하는 패턴 치환하기

  • 원하는 패턴의 문자열을 다른 문자열로 치환하는 작업은 std::regex_replace로 할 수 있다.
  • 원하는 패턴은 위에서 사용한 캡처 그룹을 활용한다. ex) sk-circle(\d)
  • 그렇게 캡처그룹을 이제 원하는 문자열로 치환하게 되는데 첫번째 캡처 그룹을 $1이라고 표현한다
    ㄴ 그 $1으로 원하는 문자 패턴을 만들 수 있다 ex) $1-sk-circle

    캡처 그룹이 여러개 일 때 어떤게 어떤 캡처그룹인지 파악하냐?
    캡처 그룹 () 괄호가 열린 순서대로 $1, $2 번호가 매겨진다.


  • 원하는 문자열을 만들긴 했는데 치환은 어떻게 하냐???
    std::regex_replace(str, re, "$1-sk-circle");

    첫번째 인자로 원본 문자열을 2번 째 인자는 정규 표현식 객체를 마지막 인자는 원하는 패턴을 전달하면 된다.



공부한 내용 복습

개인 공부 기록용 블로그입니다.
틀린 부분 있으다면 지적해주시면 감사하겠습니다!!

0개의 댓글