References
- Learning Regular Expressions
Contents
- 문자 그대로 찾기
- 모든 문자 찾기
- 특수문자 찾기
이번 포스팅에서는 한 개 혹은 그 이상의 문자를 사용해 간단한 문자 검색을 어떻게 하는지 살펴봅니다.
본문에서 정규표현식 테스트는 아래의 사이트에서 수행했습니다.
포스팅 후반부에서는 C++과 파이썬으로 살펴봤던 예제들을 어떻게 적용하는지 살펴보도록 하겠습니다.
문자 그대로 찾기
'Ben'은 정규표현식이라고 할 수 있습니다. 평범한 텍스트여서 정규표현식처럼 보이지 않을지 몰라도 확실한 정규표현식입니다. 평범한 텍스트도 정규 표현식이 될 수 있습니다. 솔직히 처리 과정을 보면 평범한 텍스트만으로 이루어진 정규표현식은 완전히 낭비라고 할 수 있지만, 일단 정규표현식을 처음 알아가기에는 좋습니다.
Hello, my name is Ben. Please visit
my website at http://www.forta.com/.
그럼 먼저 위의 예문에서 정규표현식 'Ben'으로 검색한 결과는 다음과 같습니다.
포스팅 처음에 언급한 사이트에서 테스트했는데, 일치하는 텍스트에 음영 처리가 됩니다.
다음은 정규표현식을 'Ben'이 아닌 'my'로 테스트한 결과입니다.
'my'도 'Ben'과 마찬가지로 정적 텍스트(static text)지만, 이번에는 예문에서 2개의 일치하는 결과가 발생했습니다.
대다수의 정규표현식 엔진은 기본으로 가장 처음 일치한 텍스트를 반환합니다. 바로 위의 예제의 경우, 보통 첫 번째 my만 일치(match)하고 두 번째 my는 일치하지 않았을 것입니다.
그렇다면 여기서는 왜 2개가 일치된 것일까요?
대다수 정규표현식 구현에서는 일치하는 목록을 모두 얻을 수 있습니다. 대개 배열이나 다른 특별한 형태로 반환됩니다. 예를 들어, 자바스크립트에서는 일치된 결과물을 모두 담은 배열을 반환하고자할 때, g(global) 플래그를 사용합니다.
파이썬의 경우에도 re.findall()을 통해 일치하는 문자열을 리스트로 얻을 수 있습니다.
대소문자 다루기
정규표현식에서는 대소문자를 구분하기 때문에 'Ben'은 'ben'과 일치하지 않는다고 인식합니다. 하지만 대다수 정규표현식 구현에는 대소문자 구분을 무시하는 기능이 있습니다. 예를 들어, 자바스크립트 유저는 i 플래그를 사용해 대소문자 구분을 무시하고 검색할 수 있습니다. 파이썬이나 C++의 경우에는 대소문자를 무시하는 플래그가 존재합니다.
모든 문자 찾기
방금까지는 정규표현식으로 정적 텍스트만 찾았습니다. 다소 시시할 수 있었는데, 이번에는 모르는 문자까지 찾는 방법을 알아보겠습니다.
정규 표현식에서는 특별한 문자들(혹은 문자 집합)을 사용해서 무엇을 검색할 지 결정합니다. 여기서 마침표(.) 문자는 아무 문자 하나와 일치합니다.
따라서, 'c.t'를 검색하면 cat과 cot를 비롯해 무수히 많은 단어들과 일치합니다.
다음 예문을 통해 살펴보겠습니다.
sales1.xls
orders3.xls
sales2.xls
sales3.xls
apac1.xls
europe2.xls
na1.xls
na2.xls
sa1.xls
위 예문에서 정규표현식 'sales.'으로 검색하면 다음과 같은 결과를 얻을 수 있습니다.
여기서 사용한 'sales.'라는 정규표현식은 sales로 시작하고, sales 바로 뒤에 아무 문자가 하나 더 붙는 파일명을 모두 찾습니다. 예문에서 9개의 파일 중에 3개가 이 패턴에 일치합니다.
정규표현식을 지칭하는 말로 패턴이라는 말을 자주 접하게 됩니다.
정규표현식 패턴과 문자열의 일치 영역에 주의합니다. 일치 영역(match)은 문자열 전체가 아닌 패턴과 일치하는 문자들인 경우가 많습니다(이것이 문자열 전체와 동일할 수는 있습니다). 여기서 본 예제에서 정규표현식은 전체 파일명과 일치하는 것이 아니라 파일명의 일부와 일치하였습니다. 이런 차이는 정규표현식 결과를 다른 코드나 어플리케이션에 넘겨 처리할 때 중요하기 때문에 주의해야 합니다.
마침표(.)는 어떠한 문자나 알파벳, 숫자, 심지어 문장 부호로 쓰이는 마침표 자체와도 일치합니다.
위의 예문에는 sales.xls 파일이 추가되었습니다. 마침표(.) 문자는 어느 문자와도 일치하므로 이 파일 역시 sales. 패턴과 일치합니다.
마침표(.) 여러 개를 동시에 사용할 수도 있습니다. 예를 들어, '..'처럼 연속해서 사용하면 어떤 문자든 붙어 있는 문자 두 개와 일치합니다. 물론 다른 위치에서도 사용할 수 있습니다.
바로 위에서 사용한 예문을 그대로 사용하고, 다른 패턴을 사용해보겠습니다. 이번에는 뒤에 어떤 숫자가 오든지 관계없이 북아메리카(na)나 남아메리카(sa)와 관련된 파일을 찾도록 '.a.' 패턴을 사용합니다.
정규표현식 '.a.'은 실제로 na1, na2, sa1을 검색하려고 한 것이지만, 생각지도 않았던 다른 문자열과도 일치하였습니다. 이 패턴에서 세 문자 중간에 a가 포함되기만 하면 어떠한 문자열과도 일치하도록 정규표현식을 만들었기 때문입니다.
그렇다면 '.a.' 다음에 바로 마침표가 따라오는 문자열과 일치시키려면 어떻게 해야 할까요?
이번에는 '.a..'로 검색해보겠습니다.
'.a..'도 '.a.'과 다르지 않습니다. 여기서 우리는 마지막에 마침표가 붙은 글자와 일치시키고 싶었던 것인데, 아무 문자가 하나 더 붙은 텍스트를 찾았습니다.
따라서, 원하는 문자열을 일치시키기 위해서 우리는 마침표(.)와 같은 특수문자만 검색하는 방법이 필요합니다.
특수문자 찾기
마침표(.)는 정규표현식에서 특별한 의미가 있습니다. 따라서 우리가 마침표(.)를 찾으려 한다면, 정규표현식에서 특별한 의미의 마침표가 아니라 진짜 마침표를 찾고 싶다고 알려주어야 합니다.
이는 마침표 앞에 역슬래시(\) 문자를 붙이면 됩니다.
역슬래시(\)는 메타 문자(일반적으로 문자 그대로 사용되지 않고 특별한 의미를 지니는 문자를 의미)입니다. 따라서 마침표(.)는 모든 문자와 일치하지만, '\.'는 마침표(.) 문자 자체와 일치한다는 의미입니다.
그럼 이번에는 '.'을 '\.'로 변경해서 위의 예문에 적용해보겠습니다.
'.a.\.' 정규표현식으로 원하는 파일들을 찾을 수 있게 되었습니다. 사용한 정규표현식에서 첫 번째 마침표는 n 또는 s와 일치하였습니다. 두 번째 마침표는 1 또는 2와 일치하였으며, '\.'은 파일명과 확장자를 구분하는 마침표(.)와 일치하였습니다.
이를 더 확장시켜서 생각해보면 'xls'를 패턴에 추가하여 sa3.doc와 같은 파일명과 일치하는 상황을 방지할 수 있습니다.
위 결과는 '.a.\.xls' 정규표현식으로 검색한 결과입니다. 'xls'이전까지는 위에서 설명한 것과 동일하며, 'xls'는 문자 그대로 일치하는 것을 찾습니다. 사실 'xls'가 없어도 원하는 결과가 나오지만, 여러 확장자가 있을 때를 방지한다는 의미로 추가해보았습니다.
정규표현식에서 역슬래시 문자는 주로 특별한 의미를 지니는 문자의 맨 앞에 표시합니다. 이런 문자는 한 글자 이상인 경우도 있습니다. 이번 포스팅에서는 '\.'만 살펴봤지만, 다른 포스팅에서 역슬래스 문자를 사용하는 방법을 더 접해 볼 예정입니다. 참고로 역슬래시 자체를 문자열에서 찾고 싶을 때는 '\\'를 사용하면 됩니다.
마침표(.)는 정말 모든 문자와 일치하는 것인지 의심이 들 수 있습니다. 사실 일치하지 않는 경우도 있습니다. 하지만 대다수 정규표현식 구현에서 마침표(.)는 줄바꿈(newline) 문자를 제외한 모든 문자와 일치합니다.
정규표현식 또는 패턴은 문자들로 이루어진 문자열입니다. 이러한 문자들은 그냥 의미 그대로의 문자(text)일 수도 있고, 메타 문자(특별한 의미를 지닌 특수문자)일 수도 있습니다. 이번 포스팅에서는 아주 심플한 문자 그대로 찾는 방법과 메타 문자를 사용해서 문자 하나를 일치시키는 방법을 알아봤습니다. 마침표(.)는 어떤 문자와도 일치하며, 역슬래시(\)는 문자들이 문자 그대로 해석되게 하며(이스케이프), 특수한 문자 시퀀스를 시작하는 데 사용됩니다.
C++ 예제
#include <iostream>
#include <string>
#include <regex>
void pattern_find(const std::string& str, const std::string& pattern, bool ignore_case = false)
{
std::regex::flag_type flags{};
if (ignore_case)
flags |= std::regex::icase;
std::regex re(pattern, flags);
std::cout << "\n<Pattern: \"" << pattern << "\">\n";
if (std::regex_search(str, re)) {
std::cout << "- Found\n";
}
else {
std::cout << "- Not Found\n";
}
}
void pattern_find_all(const std::string& str, const std::string& pattern)
{
std::regex re(pattern);
std::smatch match;
std::string tmp{ str };
bool found{ false };
std::cout << "\n<Pattern: \"" << pattern << "\">\n";
while (std::regex_search(tmp, match, re)) {
std::cout << "- \"" << match.str() << "\" Found\n";
tmp = match.suffix();
found = true;
}
if (!found) {
std::cout << "- Not Found\n";
}
}
int main(void)
{
std::string ex1 = R"(Hello, my name is Ben. Please visit
my website at http://www.forta.com/.)";
std::cout << "Example 1: \n\"" << ex1 << "\"\n";
pattern_find(ex1, "Ben");
pattern_find_all(ex1, "my");
pattern_find(ex1, "ben", false);
pattern_find(ex1, "ben", true);
std::cout << "\n----------------------------------------------------------\n\n";
std::string ex2 = R"(sales1.xls
orders3.xls
sales2.xls
sales3.xls
apac1.xls
europe2.xls
na1.xls
na2.xls
sa1.xls)";
std::cout << "Example 2: \n\"" << ex2 << "\"\n";
pattern_find_all(ex2, "sales.");
pattern_find_all(ex2, ".a.");
pattern_find_all(ex2, ".a..");
pattern_find_all(ex2, ".a.\\.");
pattern_find_all(ex2, R"(.a.\.xls)");
}
파이썬 예제
import re
def pattern_find(STR, PATTERN, ignore_case = False):
flags = re.I if ignore_case else 0
pattern = re.compile(PATTERN, flags=flags)
print(f"\n<Pattern: {PATTERN}>")
match = pattern.search(STR)
if match:
print("- Found")
print(f"match: {match}")
else:
print("- Not Found")
def pattern_find_all(STR, PATTERN):
pattern = re.compile(PATTERN)
found = False
print(f"\n<Pattern: {PATTERN}>")
match = pattern.findall(STR)
for m in match:
print(f'- "{m}" Found')
found = True
if not found:
print("- Not Found")
if __name__ == '__main__':
EX1 = """Hello, my name is Ben. Please visit
my website at http://www.forta.com/."""
print(f'Example 1: \n"{EX1}"')
pattern_find(EX1, "Ben")
pattern_find_all(EX1, "my")
pattern_find(EX1, "ben", False)
pattern_find(EX1, "ben", True)
print('\n' + '-' * 60 + '\n')
EX2 = """sales1.xls
orders3.xls
sales2.xls
sales3.xls
apac1.xls
europe2.xls
na1.xls
na2.xls
sa1.xls"""
print(f'Example 2: \n"{EX2}"')
pattern_find_all(EX2, "sales.");
pattern_find_all(EX2, ".a.");
pattern_find_all(EX2, ".a..");
pattern_find_all(EX2, ".a.\.");
pattern_find_all(EX2, ".a.\.xls");
'프로그래밍 > 정규표현식' 카테고리의 다른 글
[REGEX] 하위 표현식 (Subexpression) (0) | 2022.04.08 |
---|---|
[REGEX] 위치 찾기 (Position Matching) (0) | 2022.04.07 |
[REGEX] 반복 찾기 (0) | 2022.04.06 |
[REGEX] 메타 문자 (0) | 2022.04.05 |
[REGEX] 문자 집합으로 찾기 (0) | 2022.04.04 |
댓글