1. 위치 찾기
가끔 텍스트 영역 내에 있는 특정 위치에서 텍스트를 찾아야 할 때가 있다.
위치 찾기는 텍스트 문자열 안에서 반드시 일치해야 하는 위치를 지정할 때 사용한다.
아래 예제를 보면서 위치 찾기에 대한 감을 잡아보자.
[예문]
The cat scattered his food all over the room.
[정규 표현식]
cat
[결과]
The cat scattered his food all over the room.
위 예제에서 찾으려 했던 것은 “cat”이나, cat이 포함된 scattered의 일부 역시 일치해 버렸다.
정규 표현식 cat이 “cat”이 있는 모든 부분과 일치해 버렸기 때문이다.
혹여, “cat”이라는 단어를 “dog”로 바꾸려 한다면 문제가 아닐 수 없다.
이럴 때 경계를 사용하거나 정규 표현식 앞뒤에 특정한 위치 혹은 경계를 나타내는 앵커(anchor) 메타 문자를 사용해 위치를 정해주어야 한다.
앵커 메타 문자들로는 다음과 같은 것들이 있다.
- \b <-> \B
- ^
- $
그리고 이것은 아주 중요한 내용인데,
앵커 메타 문자들은 실제로 문자와 일치하는 것이 아니고, 위치를 가리키는 것이다.
아래에 소개할 단어 경계 앵커를 사용해 \bcat\b으로 “cat”을 찾을 경우
cat 앞 뒤의 \b는 위치만 가리키지 단어와 일치하지 않기에 결과는 ‘c’,’a’,’t’ 세 글자인 것이다.
2. 단어 경계
일반적으로 가장 많이 사용하는 것이 단어 경계이다.
단어 경계는 \b로 표시하며, 단어의 시작이나 마지막을 일치시킬 때 사용한다.
주로, \b를 단어 시작과 마지막에 붙여 정규 표현식에서 찾고자 하는 단어와 완전히 일치시킬 목적으로 사용한다.
위 예제를 단어 경계를 사용하여 다시 작성해 보자.
[정규 표현식]
\bcat\b
[결과]
The cat scattered his food all over the room.
우리가 찾으려 했던 것은 정확히 “cat”과 일치하는 단어였다.
scattered의 cat 앞뒤로는 각각 ‘s’와 ‘t’이므로 공백이 아니어서 일치 대상에서 제외되었다.
그럼, \b를 단어의 앞과 뒤에만 사용하는 예제도 살펴보도록 하자.
단어 앞에 \b 사용시 단어로 시작하는 문자열을 찾을 것이고,
단어 뒤에 \b 사용시 단어로 끝나는 문자열을 찾을 것이다.
[예문]
The captain wore this cap and cape proudly as
he sat listening to the recap of how his
crew saved the man from capsized vessel.
[정규 표현식]
\bcap
[결과]
The captain wore this cap and cape proudly as
he sat listening to the recap of how his
crew saved the man from capsized vessel.
[정규 표현식]
cap\b
[결과]
The captain wore this cap and cape proudly as
he sat listening to the recap of how his
crew saved the man from capsized vessel.
그리고, 특별히 단어 경계와 일치시키고 싶지 않을 땐, \B를 사용한다.
지금까지의 메타 문자들이 그랬듯이, 대문자 메타 문자는 소문자 메타 문자의 반대 의미를 가진다.
[정규 표현식]
\Bcap
[결과]
The captain wore this cap and cape proudly as
he sat listening to the recap of how his
crew saved the man from capsized vessel.
\Bcap라고 하였으니, 이를 해석하면 cap으로 시작하지 않는 것을 일치시킨다 정도가 될 것이다.
따라서, 위 예문에서는 이에 해당하는 부분이 recap 뿐이다.
정리하자면, 단어 경계(\b)는 단어의 위치(시작, 끝, 그리고 전체 단어)를 기반으로 위치를 찾는다.
\B는 정확히 \b의 반대 기능을 수행한다.
3. 문자열 경계
문자열 경계는 전체 문자열의 위치(시작이나 마지막 부분)를 기반으로 위치를 찾는다.
- 문자열의 시작 위치 : ^(캐럿)
- 문자열의 마지막 위치 : $(달러)
^(캐럿)은 문자 집합([])안에서 사용시 해당 집합 내 모든 문자를 일치 대상에서 제외시키는 기능을 가지고 있다.
(“정규 표현식 – 문자 집합으로 찾기” 문서의 “제외하고 찾기” 챕터 참고)
집합 밖에서 정규 표현식의 시작 부분에 ^(캐럿) 메타 문자를 사용하면, 문자열의 시작 부분과 일치한다.
1) 시작 위치
XML 문서의 헤더가 올바르게 포함되어 있는지 체크하는 예제를 살펴보면서 ^(캐럿) 사용법에 대해 익혀보자.
[예문]
<?xml version=”1.0″ encoding=”UTF-8″ ?>
<unattend>
<settings pass=”offline” />
</unattend>
[정규 표현식]
\<\?xml.*?\?\>
[결과]
<?xml version=”1.0″ encoding=”UTF-8″ ?>
<unattend>
<settings pass=”offline” />
</unattend>
위에서 사용했던 정규 표현식을 하나씩 까보면…
- < 을 표현하기 위한 \< (이스케이프)
- ? 을 표현하기 위한 \? (이스케이프)
- xml
- 없거나 하나 이상의 문자를 위한 게으른 수량자 .*?
- ? 을 표현하기 위한 \? (이스케이프)
- > 을 표현하기 위한 \> (이스케이프)
음… 당장의 결과만 놓고 보면 지금의 정규 표현식으로도 충분히 xml 헤더 검증을 할 수 있을 것 같지만,
다음의 두 가지 규칙을 충족시키지 못했다.
- XML 문서의 첫 라인에 헤더가 위치할 것
- 헤더 시작 이전에 공백이 포함될 수 있음
따라서, ^(캐럿)을 이용해 헤더가 텍스트 영역 내 시작 부분에 위치하는지 체크해 보아야 한다.
그리고, 헤더 앞에 존재할 수 있는 공백 처리를 위해 \s* (공백이 없거나 하나 이상)도 추가한다.
[예문]
Incorrect!!!
<?xml version=”1.0″ encoding=”UTF-8″ ?>
<unattend>
<settings pass=”offline” />
</unattend>
[정규 표현식]
^\s*\<\?xml.*?\?\>
[결과]
Incorrect!!!
<?xml version=”1.0″ encoding=”UTF-8″ ?>
<unattend>
<settings pass=”offline” />
</unattend>
^(캐럿)을 이용해 시작 위치에 대한 체크를 추가하였기에, 위 예문과 정규 표현식은 아무런 일치 결과를 얻지 못했다.
2) 마지막 위치
$(달러) 역시 ^(캐럿)과 유사한 방법으로 사용한다.
^(캐럿)이 문자열의 시작 위치를 찾기 위해 정규 표현식의 제일 앞에 위치했다면,
$(달러)는 문자열의 마지막 위치를 찾기 위해 정규 표현식의 제일 마지막에 위치한다.
아래 예제는 웹 페이지에서 닫는 </html> 태그 뒤 공백을 제외한 아무 문자도 나오지 않는지 확인한다.
[정규 표현식]
\<\/[Hh][Tt][Mm][Ll]\>\s*$
근데 사실 \s*$는 거의 항상 일치하기 때문에, 자주 사용되진 않는다.
어느 문자열이 \s*$로 끝나지 않겠는가?
4. 멀티라인 모드
^(캐럿)은 전체 문자열의 시작 위치와 일치하고, $(달러)는 전체 문자열의 마지막 위치와 일치한다.
위치를 찾는 대상이 전체 문자열이기에, 멀티 라인 문자열의 경우 첫 라인과 마지막 라인에만 ^(캐럿)과 $(달러)가 반응한다.
멀티라인으로 구성된 문자열에서 ^(캐럿)과 $(달러)가 라인별 시작과 끝 위치를 일치시키게 하려면, 정규 표현식을 멀티라인 모드로 변경시켜야 한다.
그 역할을 하는 것이 바로 (?m)메타 문자이다.
(?m) 메타 문자는 줄바꿈 메타 문자들(\r?\n)을 문자열 델리미터로 인지해 라인 단위로 정규 표현식을 적용한다.
참고로, (?m)은 반드시 정규 표현식의 제일 앞에 위치해야 하며, 이를 지원하지 않는 정규 표현식 엔진도 적지 않다고 한다.
(자신의 사용하는 정규 표현식 엔진이 멀티라인 모드를 지원하는지, 지원한다면 어떤 방법을 쓰는지 알아봐야 될 듯 하다)
그럼 전체 문자열에서 라인 단위(by 뉴라인 문자)로 시작과 끝 위치를 찾아보아야 하는 예제를 살펴보자.
소스 코드에서 특정한 키워드를 찾거나 주석 등을 찾을 때 유용할 듯 하다.
아래 예제는 주석 부분이 아닌 enum을 선언하는 키워드만 검색하려는 예제이다.
[예문]
enum AAA // this is AAA enum
{
};
struct BBB
{
enum CCC
{
};
};
enum DDD // this is DDD enum
{
};
[정규 표현식]
(?m)^\s*enum
[결과]
enum AAA // this is AAA enum
{
};
struct BBB
{
enum CCC
{
};
};
enum DDD // this is DDD enum
{
};
만약 위 예제에서 정규 표현식을 멀티라인 모드로 만들지 않았다면(즉, (?m)으로 시작하지 않았다면)
젤 첫 라인의 enum 키워드만 제대로 일치되었을 것이다.
공백 처리를 위해 \s*을 사용한 부분 역시 놓치지 말자.
5. 정리
정규 표현식은 텍스트의 어떤 구역이나 문자열의 특정 위치에서도 텍스트를 찾을 수 있도록 다양한 앵커 메타 문자를 지원한다.
- \b는 단어 경계를 지정할 때 사용한다.
- \B는 \b의 완전히 반대되는 의미이다.
- ^(캐럿)과 $(달러)는 문자열 경계(시작과 끝 위치)를 나타낸다.
- (?m)은 정규 표현식을 멀티라인 모드로 변경해, ^(캐럿)과 $(달러)가 라인 단위로 시작과 끝을 나타내게 해 준다.