이번 글에서는 C++에서 정규표현식(Regular Expression)을 사용하는 방법에 대해 알아보겠습니다.
정규표현식의 사전적 의미로는 ‘특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어’ 입니다.
보통 프로그래밍에서는 문자열에서 패턴의 매칭 여부나, 검색, 문자열을 변경하는데 사용됩니다.
아래는 대표적으로 사용되는 정규표현식의 문법 목록입니다.
문법 | 설명 |
---|---|
. | 임의의 한 문자 |
^ | 문자열 시작 |
$ | 문자열 끝 |
* | 앞의 문자가 0번 이상 반복 |
+ | 앞의 문자가 1번 이상 반복 |
? | 앞의 문자가 0번 또는 1번 나타남 |
{n} | 앞의 문자가 n번 나타남 |
{n,} | 의 문자가 n번 이상 나타남 |
{n, m} | 앞의 문자가 n번 이상 m번 이하 나타남 |
[] | 괄호 안에 있는 문자 중 하나를 매치 |
[-] | 문자 범위. [a-d]일 경우 a, b, c, d 중 하나를 매치 |
[^] | 괄호 안에 있는 문자 이외의 것과 매치 |
() | Grouping(그룹핑) |
| | or 연산. |
\d | 숫자와 매치, [0-9]와 동일 |
\D | 숫자가 아닌 것과 매치, [^0-9]와 동일 |
\w | 문자와 숫자 매치, [a-zA-Z0-9]와 동일 |
\W | 문자와 숫자가 아닌것과 매치, [^a-zA-Z0-9]와 동일 |
\s | 공백문자와 매치, [\t\n\r\f\v]와 동일 |
\S | 공백문자가 아닌 것과 매치, [^\t\n\r\f\v]와 동일 |
위 문법으로 정규표현식을 만들면 됩니다.
regex_match
정규표현식을 생성하여 매칭하는 방법에 대해 알아보겠습니다. 어느 문자열에서 정해진 패턴이 매치 되는지 안되는지 알아볼 때 regex_match 함수를 사용합니다.
예를 들어 어느 프로그램에서 아래 패턴과 같은 이름을 가지는 로그 파일을 매일 저장한다고 가정합니다.
- (년)-(월)-(일).log
- 년도는 4자리 숫자
- 월은 2자리 숫자
- 일을 2자리 숫자
위 패턴에 매칭하는 문자열은 아래 같은 문자열이여야 합니다.
- 2023-12-01.log
- 2023-12-02.log
- 2023-12-04.log
어쨌든 위와 같은 패턴으로 정규표현식을 만들면 아래와 같습니다.
std::regex regex_filename_log(R"(\d{4}-\d{2}-\d{2}[.]log)");
아래는 위 패턴을 가지고 regex_match 함수를 사용한 예 입니다.
std::regex regex_filename_log(R"(\d{4}-\d{2}-\d{2}[.]log)");
// match 예제 1
std::string strFilename = "2023-04-15.txt"; // 매치 안됨. log가 아님
if (std::regex_match(strFilename, regex_filename_log))
std::cout << "Matched" << std::endl;
else
std::cout << "Not matched" << std::endl;
strFilename = "23-04-15.log"; // 매치 안됨. 년이 4글자가 아님
if (std::regex_match(strFilename, regex_filename_log))
std::cout << "Matched" << std::endl;
else
std::cout << "Not matched" << std::endl;
// match 예제 2
strFilename = "2023-01-10.log"; // 매치 됨
if (std::regex_match(strFilename, regex_filename_log))
std::cout << "Matched" << std::endl;
else
std::cout << "Not matched" << std::endl;
위 코드를 실행하면 아래와 같이 출력합니다.
Not matched
Not matched
Matched
regex_match - Grouping
매치되는 파일 이름 중에 년, 월, 일만 따로 뽑고 싶을 경우가 있을 수 있습니다.
이럴 때엔 소괄호'()’를 사용하여 그룹핑 하여 매치하면 됩니다.
std::regex regex_group_filename_log(R"((\d{4})-(\d{2})-(\d{2})[.]log)"); // Grouping 하려면 () 사용
std::smatch match_filename;
if (std::regex_match(strFilename, match_filename, regex_group_filename_log))
{
std::cout << "match string: " << match_filename[0].str() << std::endl;
std::cout << "year: " << match_filename[1].str() << std::endl;
std::cout << "month: " << match_filename[2].str() << std::endl;
std::cout << "day: " << match_filename[3].str() << std::endl;
}
std::smatch에 그룹핑 한 정보가 담겨있습니다. 출력 결과는 아래와 같습니다.
match string: 2023-01-10.log
year: 2023
month: 01
day: 10
regex_search
문자열 내에서 패턴을 검색하는 방법은 regex_search 함수를 사용하면 됩니다. 해당 함수를 사용하면 문자열 내에서 패턴을 검색 합니다.
아래는 샘플 코드입니다.
// 검색 예제 1
strFilename = "파일 이름: 2023-01-10.log";
if (std::regex_search(strFilename, match_filename, regex_filename_log))
std::cout << "Searched: " << match_filename.str() << std::endl;
// 검색 예제 2
strFilename = "파일 목록: 2023-04-15.log,2022-09-28.txt,2023-01-10.log,2022-06-22.log,2023-11-05.txt,2022-03-17.txt";
std::cout << "Searched List" << std::endl;
while (std::regex_search(strFilename, match_filename, regex_filename_log))
{
std::cout << match_filename.str() << std::endl;
strFilename = match_filename.suffix();
}
위 코드를 실행하면 아래처럼 출력합니다.
Searched: 2023-01-10.log
Searched List
2023-04-15.log
2023-01-10.log
2022-06-22.log
regex_match와 크게 다르지 않고 문법도 어렵지 않습니다.
regex_iterator
regex_iterator는 regex_search를 편하게 하기 위한 함수입니다. 사용법은 아래와 같습니다.
strFilename = "파일 목록: 2023-04-15.log,2022-09-28.txt,2023-01-10.log,2022-06-22.log,2023-11-05.txt,2022-03-17.txt";
std::sregex_iterator start = std::sregex_iterator(strFilename.begin(), strFilename.end(), regex_filename_log);
std::sregex_iterator end = std::sregex_iterator();
while (start != end)
{
std::cout << start->str() << std::endl;
++start;
}
sregex_iterator는 std::string에 대한 반복자를 사용하는 regex_iterator입니다. 위 코드를 실행하면 아래와 같이 출력합니다.
2023-04-15.log
2023-01-10.log
2022-06-22.log
regex_replace
regex_replace는 매치된 패턴을 치환하는 함수입니다.
예를 들어 문자열 내에서 년-월-일 형식으로 되어있는 파일 이름을 일-월-년 형식으로 바꿔야 하는 경우가 있습니다.
그럴 때엔 위에서 알아본 그룹핑과 regex_replace 함수를 사용하면 편하게 가능합니다.
우선 치환하려고 하는 부분을 그룹핑 해줍니다.
(\d{4})-(\d{2})-(\d{2})[.](\w+)
앞에서 년, 월, 일, 파일 확장자 순서대로 그룹핑이 되었는데 아래와 달러 문자에 숫자를 붙여 문자열에 명시하면 regex_replace에서 역참조하여 치환해줍니다.
$1, $2, $3, $4
코드로 보면 다음과 같습니다.
strFilename = "파일 목록: 2023-04-15.log,2022-09-28.txt,2023-01-10.log,2022-06-22.log,2023-11-05.txt,2022-03-17.txt";
std::regex regex_replace_filename_log(R"((\d{4})-(\d{2})-(\d{2})[.](\w+))"); // Grouping 하려면 () 사용
std::smatch match;
std::string strFilename_New = std::regex_replace(strFilename, regex_replace_filename_log, "$3-$2-$1.$4"); // $1: 년, $2: 월, $3: 일, $4: 파일 확장자
std::cout << strFilename << std::endl;
std::cout << strFilename_New << std::endl;
아래는 실제 출력 결과입니다.
파일 목록: 2023-04-15.log,2022-09-28.txt,2023-01-10.log,2022-06-22.log,2023-11-05.txt,2022-03-17.txt
파일 목록: 15-04-2023.log,28-09-2022.txt,10-01-2023.log,22-06-2022.log,05-11-2023.txt,17-03-2022.txt
regex_replace
전체 코드는 아래와 같습니다.
#include <iostream>
#include <regex>
#include <vector>
int main()
{
////////////////////////////// regex_match
std::cout << std::endl << "////////////////////////////// regex_match" << std::endl;
std::regex regex_filename_log(R"(\d{4}-\d{2}-\d{2}[.]log)");
// match 예제 1
std::cout << "ex 1)" << std::endl;
std::string strFilename = "2023-04-15.txt"; // 매치 안됨. log가 아님
if (std::regex_match(strFilename, regex_filename_log))
std::cout << "Matched" << std::endl;
else
std::cout << "Not matched" << std::endl;
strFilename = "23-04-15.log"; // 매치 안됨. 년이 4글자가 아님
if (std::regex_match(strFilename, regex_filename_log))
std::cout << "Matched" << std::endl;
else
std::cout << "Not matched" << std::endl;
// match 예제 2
std::cout << "ex 2)" << std::endl;
strFilename = "2023-01-10.log"; // 매치 됨
if (std::regex_match(strFilename, regex_filename_log))
std::cout << "Matched" << std::endl;
else
std::cout << "Not matched" << std::endl;
// match 예제 3 - Grouping
std::cout << "ex 3)" << std::endl;
std::regex regex_group_filename_log(R"((\d{4})-(\d{2})-(\d{2})[.]log)"); // Grouping 하려면 () 사용
std::smatch match_filename;
if (std::regex_match(strFilename, match_filename, regex_group_filename_log))
{
std::cout << "match string: " << match_filename[0].str() << std::endl;
std::cout << "year: " << match_filename[1].str() << std::endl;
std::cout << "month: " << match_filename[2].str() << std::endl;
std::cout << "day: " << match_filename[3].str() << std::endl;
}
////////////////////////////// regex_search
std::cout << std::endl << "////////////////////////////// regex_search" << std::endl;
// 검색 예제 1
std::cout << "ex 1)" << std::endl;
strFilename = "파일 이름: 2023-01-10.log";
if (std::regex_search(strFilename, match_filename, regex_filename_log))
std::cout << "Searched: " << match_filename.str() << std::endl;
// 검색 예제 2
std::cout << "ex 2)" << std::endl;
strFilename = "파일 목록: 2023-04-15.log,2022-09-28.txt,2023-01-10.log,2022-06-22.log,2023-11-05.txt,2022-03-17.txt";
std::cout << "Searched List" << std::endl;
while (std::regex_search(strFilename, match_filename, regex_filename_log))
{
std::cout << match_filename.str() << std::endl;
strFilename = match_filename.suffix();
}
////////////////////////////// regex_iterator
std::cout << std::endl << "////////////////////////////// regex_iterator" << std::endl;
// interator 예제 1
std::cout << "ex 1)" << std::endl;
strFilename = "파일 목록: 2023-04-15.log,2022-09-28.txt,2023-01-10.log,2022-06-22.log,2023-11-05.txt,2022-03-17.txt";
std::sregex_iterator start = std::sregex_iterator(strFilename.begin(), strFilename.end(), regex_filename_log);
std::sregex_iterator end = std::sregex_iterator();
while (start != end)
{
std::cout << start->str() << std::endl;
++start;
}
////////////////////////////// regex_replace
std::cout << std::endl << "////////////////////////////// regex_replace" << std::endl;
std::cout << "ex 1)" << std::endl;
strFilename = "파일 목록: 2023-04-15.log,2022-09-28.txt,2023-01-10.log,2022-06-22.log,2023-11-05.txt,2022-03-17.txt";
std::regex regex_replace_filename_log(R"((\d{4})-(\d{2})-(\d{2})[.](\w+))"); // Grouping 하려면 () 사용
std::smatch match;
std::string strFilename_New = std::regex_replace(strFilename, regex_replace_filename_log, "$3-$2-$1.$4"); // $1: 년, $2: 월, $3: 일, $4: 파일 확장자
std::cout << strFilename << std::endl;
std::cout << strFilename_New << std::endl;
return 0;
}