본문 바로가기
프로그래밍/정규표현식

[REGEX] 위치 찾기 (Position Matching)

by 별준 2022. 4. 7.

References

  • Learning Regular Expressions

Contents

  • 단어 경계 지정하기
  • 문자열 경계 정의하기
  • Multiline Mode

텍스트를 찾을 때, 텍스트 영역 내에 있는 특정 위치에서 텍스트를 찾아야 할 때도 있습니다. 이런 경우에 위치 찾기(position matching)이 필요하며, 이번 포스팅에서는 이와 관련한 내용을 살펴보겠습니다.

 

 


경계 지정하기

위치 찾기는 텍스트 문자열 안에서 반드시 일치해야 하는 위치를 지정할 때 사용합니다. 이러한 기능이 왜 필요한지 이해하기 위해서 다음 예문을 통해 살펴보겠습니다.

The cat scattered his food all over the room.

위 예문에서 cat을 찾기 위해서 'cat' 패턴을 사용해봅시다. 그 결과는 다음과 같습니다.

'cat' 패턴은 cat이 있는 부분과 모두 일치합니다. 심지어 scattered라는 단어 사이에 있는 cat과도 일치합니다. 사실 이런 결과를 원했을 수도 있지만, 그렇지 않을 가능성이 더 큽니다. 만약 cat을 log로 치환하려고 검색했다면 잘못된 결과를 얻게 될 것 입니다.

 

이런 경우에 경계(boundaries)를 사용하거나 패턴 앞이나 뒤에 특정한 위치 혹은 경계를 나타내는 메타 문자를 사용하면 됩니다.

 


단어 경계 지정하기

처음으로 알아볼 경계는 가장 흔하게 사용하는 방법인데, '\b'로 표시하는 단어 경계입니다. 단어 경계라는 이름에서 유추해 볼 수 있듯이, '\b'는 단어의 시작이나 마지막을 일치시킬 때 사용합니다.

앞서 본 예문에서 '\bcat\b' 패턴을 사용하면 다음의 결과를 얻을 수 있습니다.

cat이라는 단어 앞뒤는 빈칸인데 여기서 빈칸은 단어와 단어를 구분하는 문자들 가운데 하나이므로, '\bcat\b'와 일치합니다. 하지만 scattered라는 단어 사이에 있는 cat은 앞에는 s, 뒤에는 t가 이어서 나와 둘 다 '\b'와 일치하지 않으므로 일치하지 않습니다.

그럼 여기서 '\b'는 정확히 무엇과 일치하는 걸까요? 정규표현식 엔진은 영어를 비롯해 어떤 언어도 이해하지 못합니다. 따라서 무엇이 단어인지도 알지 못합니다. '\b'는 일반적으로 단어의 일부로 사용하는 문자(영숫자 문자, 밑줄, \w와 일치하는 문자)와 그 외의 문자(\W와 일치하는 문자) 사이에 있는 위치와 일치합니다.

 

중요한 것은 완전한 단어 하나를 일치시키고자 한다면, 일치시키고자 하는 단어 앞뒤에 모두 '\b'를 붙여야 한다는 점입니다. 다음 예문을 살펴보겠습니다.

The captain wore this cap and cape proudly as
he sat listening to the recap of how his
crew saved the men from a capsized vessel.

여기서 '\bcap' 패턴을 사용한 결과는 다음과 같습니다.

'\bcap' 패턴은 cap으로 시작하는 모든 단어와 일치합니다. 따라서 단어가 총 4개 일치했지만, 그 중 세 단어는 cap이 포함된 다른 단어입니다.

 

동일한 예문에서 'cap\b' 패턴을 사용한 결과는 다음과 같습니다.

'cap\b'는 cap으로 끝나는 단어와 모두 일치합니다. 따라서 단어가 총 두 개 일치했지만, 그중 하나는 cap을 포함하는 다른 단어입니다. 만약 cap만으로 구성된 단어만 일치시키려 한다면, '\bcap\b' 패턴을 사용해야 합니다.

'\b'는 실제로 문자와 일치하는 것이 아니라, 위치를 가리킵니다. 그래서 '\bcat\b'를 사용하여 찾은 문자열의 길이는 5가 아니라 3(c,a,t) 입니다.

 

특별히 단어 경계와 일치시키고 싶지 않을 때는 '\B'를 사용합니다. 다음 예문에서 사용한 정규표현식 패턴은 '\B' 메타 문자를 활용해 잘못 쓰인 빈칸으로 둘러싸인 하이픈을 찾습니다.

Please enter the nine-digit id as it
appears on your color - coded pass-key.

사용한 패턴은 '\B-\B' 입니다.

'\B-\B'는 단어 구분(word-break) 문자로 둘러싸인 하이픈과 일치합니다. 따라서, nine-digit와 pass-key에 있는 하이픈은 일치하지 않지만 color - coded에 있는 하이픈과는 일치합니다.

 


문자열 경계 정의하기

단어 경계는 단어의 위치(단어의 시작/마지막, 단어 전체 등)를 기반으로 위치를 찾습니다. 문자열 경계(String Boundaries)는 단어 경계와 기능은 비슷하지만, 전체 문자열의 시작이나 마지막 부분과 패턴을 일치시키고자 할 때 사용합니다. 문자열 경계는 메타 문자 가운데 '^'으로 문자열을 시작을, '$'로 문자열의 마지막을 나타냅니다.

'^' 문자는 집합을 부정할 때 사용되기도 합니다. 그렇다면 어떻게 이 문자로 문자열의 시작을 나타낼까요?
'^' 문자는 여러 용도로 쓰는 몇몇 메타 문자 중 하나입니다. 대괄호로 둘러싸인 집합 안에서 '[' 문자 바로 다음에 쓰면 부정을 뜻합니다. 집합 밖에서는 패턴 시작 부분에 '^'을 쓰면 문자열의 시작 부분과 일치합니다.

 

문자열 경계를 사용하는 방법을 다음 예문(XML)을 통해서 살펴보겠습니다.

<?xml version="1.0" encoding="UTF-8" ?>
<wsdl:definitions targetNamespace="http://tips.cf"
xmlns:impl="http://tips.cf" xmlns:"intf=http://tips.cf"
xmlns:apachesoap="http://xml.apache.org/xml-soap"

올바른 XML 문서는 <?xml>로 시작하고, 주로 부가 속성을 포함합니다. 아래의 패턴으로 주어진 예문이 XML 문서인지 아닌지 간단하게 검사합니다.

'<\?xml.*\?>'

결과는 다음과 같습니다.

제대로 동작하는 것처럼 보입니다. '<\?xml'은 <?xml과 일치하고, '\?>'는 마지막 ?>와 일치하며, '.*'은 텍스트가 없는 경우도 포함하여 둘 사이에 있는 모든 텍스트와 일치합니다.

 

하지만 이 검사는 부정확합니다. 다음 예문을 동일한 패턴을 적용한 결과를 살펴보겠습니다. 이 예문은 XML 파일이 시작하기 전에 관계없는 텍스트가 포함되었습니다.

This is bad, real bad!
<?xml version="1.0" encoding="UTF-8" ?>
<wsdl:definitions targetNamespace="http://tips.cf"
xmlns:impl="http://tips.cf" xmlns:"intf=http://tips.cf"
xmlns:apachesoap="http://xml.apache.org/xml-soap"

'<\?xml.*\?>' 패턴은 예문에서 두 번째 줄과 일치합니다. 그리고 실제로 예문 두 번째 줄에 XML을 여는 태그가 있긴 하지만, 결코 유효한 예문은 아닙니다. 게다가 이 예문으로 XML을 처리하려다가는 여러 문제가 발생할 수 있습니다.

 

여기서 우리는 XML을 여는 태그가 문자열 안에 있는 실제 텍스트에서 첫 번째 줄에 위치한다는 사실을 확인해야 합니다. 이 작업을 하기에는 다음과 같이 '^' 메타 문자가 적절합니다.

 

다음의 정규표현식으로 첫 줄에 XML을 여는 태그가 있는지 확인할 수 있습니다.

'^\s*<\?xml.*\?>'

첫 줄에 다른 텍스트가 있다면 일치하는 부분을 찾지 못합니다.

 

여기서 사용한 패턴의 처음에 있는 '^' 문자는 문자열의 시작과 일치하므로, '^\s*'는 문자열 시작이면서 바로 뒤에 공백 문자가 없거나, 하나 이상 있는 경우와 일치합니다. 즉, XML 문서가 시작하기 전에 나올 수 있는 빈칸, 탭, 줄바꿈들을 처리합니다. 따라서, 위의 패턴은 어떤 속성을 지닌 XML이든 여는 태그와 일치하며, 공백도 정확하게 처리합니다.

사실 여기서 사용한 패턴이 잘 동작하기는 하지만, 이는 예문으로 사용된 XML이 완전하지 않기 때문입니다. 완전히 XML 파일에 이 정규표현식을 사용하면 실제로 greedy quantifier가 어떻게 동작하는지 볼 수 있습니다. 따라서 이 예제는 '.*'가 아닌 '.*?'를 언제 사용해야 하는지 잘 보여주는 훌륭한 예시입니다.

 

'$'도 '^' 문자와 매우 유사한 방법으로 사용합니다. 다음 패턴은 웹 페이지에서 닫는 </html> 태그 뒤에 아무 문자도 나오지 않는 지 확인하는데 사용합니다.

'</[Hh][Tt][Mm][Ll]>\s*$'

 

 

Multiline Mode 사용하기

일반적으로 '^'은 문자열의 시작과 일치하고, '$'는 문자열의 마지막과 일치합니다. 하지만 예외적으로 두 메타 문자의 동작을 바꾸는 방법이 있습니다.

 

많은 정규 표현식 구현은 다른 메타 문자의 동작을 변경하는 툭수한 메타 문자를 지원하는데, 그중 하나가 (?m)으로, 다중행(multiline)을 지원합니다. 이 모드로 변경하면 정규표현식 엔진이 줄바꿈 문자를 문자열 구분자로 강제로 인식합니다. '^'은 문자열의 시작이나 줄바꿈 다음(새로운 행)에 나오는 문자열의 시작과 일치하고, '$'는 문자열의 마지막이나 줄바꿈 다음에 나오는 문자열의 마지막과 일치합니다.

 

(?m)은 항상 패턴 제일 앞에 두어야 합니다. 다음은 정규표현식을 사용하여 코드 블록 내의 모든 자바스크립트 주석을 찾는 예제입니다.

<script>
function doSpellCheck(form, field) {
    // Make sure not empty
    if (field.value == '') {
        return false;
    }
    // Init
    var windowName='spellWindow';
    var spellCheckURL='spell.cfm?formname=comment&fieldname='+field.name;
    ...
    // Done
    return false;
}
</script>

여기서 '(?m)^\s*\/\/.*$' 패턴을 적용한 결과는 다음과 같습니다.

다만, (?m)은 자바스크립트를 포함한 대부분의 정규표현식 구현에서는 지원하지 않고, 따로 플래그를 지정해주어야 합니다.

 

 

'프로그래밍 > 정규표현식' 카테고리의 다른 글

[REGEX] 역참조와 치환 작업  (0) 2022.04.09
[REGEX] 하위 표현식 (Subexpression)  (0) 2022.04.08
[REGEX] 반복 찾기  (0) 2022.04.06
[REGEX] 메타 문자  (0) 2022.04.05
[REGEX] 문자 집합으로 찾기  (0) 2022.04.04

댓글