정규 표현식 - 탐색

장동혁·2021년 4월 29일
0

정규표현식

목록 보기
1/1

Intro

프로그래밍에서 정규표현식은 매우 다양한 상황에서 사용된다. 정규표현식의 여러 문법 중 탐색(look)을 잘 사용하면 매우 유용하지만 사용 빈도가 떨어져 필요할 때 마다 구글링을 하게 되는데 이번 포스팅을 통해 외워보려고 한다.

탐색?

먼저 정규표현식의 의미를 생각해보면 긴 문자열에서 원하는 문자(열)의 형태(조건)를 "표현"하기 위한 "식"이다.

예시 : 010-1234-5678 과 같은 휴대폰 번호(문자열)에서 가운데 자리를 "표현" 하는 "식"인 정규표현식은 -\d{4}- 이다.

가운데 번호의 특징은 --사이에 위치하는 숫자 4개이기 때문에 위와 같은 표현식이 나왔다. 하지만 이 정규표현식에 대응되는 문자열은 실제 가운데 번호인 1234가 아닌 -가 포함된 -1234-이다. 가운데 번호라는 특징을 표현하기 위해 -를 사용했지만 - 자체도 문자열에 대응되어 버렸다. 이를 소비(consume)되었다고 말한다.

표현식에 포함된 일반적인 문자인 -,\d는 소비되었고 {4}는 반복이라는 특별한 기능을 하기 때문에 소비되지 않았다. 그렇다면 -가 소비되지 않게 하려면 어떻게 해야될까? 탐색을 활용하면 된다.

(?<=-)\d{4}(?=-) 이 표현식에 대응되는 문자열은 1234이다. 강조된 부분이 새로 추가된 표현식이며 탐색을 의미한다.

탐색을 위해 사용된 문자는 소비되지 않는다.

종류

탐색의 종류 다음과 같다.

  • 전방 긍정 탐색 (?=)
  • 후방 긍정 탐색 (?<=)
  • 전방 부정 탐색 (?!)
  • 후방 부정 탐색 (?<!)

전방은 후방과 대칭되며 긍정은 부정과 대칭된다. 전방후방은 탐색의 방향을 의미하며 긍정과 부정은 탐색 키워드를 긍적적으로 사용할 건지 부정적으로 사용할 건지 정한다. (자세한 설명은 해당 섹션)

전방 / 후방 긍정 탐색

전방 긍정 탐색은 탐색 키워드를 기준으로 앞에 위치한 표현식을 소비하여 대응시킨다.

표현식 : \d{4}(?=-)
대상 : 010-1234-5678 
대응된 문자열: 1234

탐색 키워드-를 기준으로 앞에 연속된 네 개의 숫자를 찾기 때문에 1234가 대응되었다. 010은 뒤에 -가 있지만 세자리이기 때문에 대응되지 못하였다.

후방 긍정 탐색은 전방 탐색과 반대로 탐색 키워드를 기준으로 뒤에 위치한 표현식을 소비하여 대응시킨다.

표현식 : (?<=-)\d{4}
대상 : 010-1234-5678 
대응된 문자열: 1234, 5678

탐색 키워드 -를 기준으로 뒤에 연속된 네 개의 숫자를 찾기 때문에 1234, 5678이 둘 다 대응된다.
(g옵션이 붙어 있지 않으면 1234만 대응)

전방 / 후방 부정 탐색

부정 탐색에서 부정이 뜻하는 의미는 주어진 키워드를 부정한다는 의미이다. 키워드를 부정한다는 뜻은 주어진 키워드 대신 반대되는 키워드 들을 사용하겠다는 의미이다. 부정의 의미이기 때문에 = 대신 !를 사용하면 된다.

표현식 : \d{4}(?!-)
대상 : 010-1234-5678
대응된 문자열: 5678

1234는 뒤에 -가 있기 때문에 대응되지못한다. 5678은 뒤에 아무것도 없기 때문에 (-가 아니기 때문에) 대응된다.

정규표현식에서 부정을 나타내는 방법 중에 [^]를 이용하는 방법이 있다. 이 방법과 긍정 탐색을 조합하면 다음과 같이 나타낼 수 있을 것이라고 예상했는데 결과는 달랐다.

표현식 : \d{4}(?=[^-])
대상 : 010-1234-5678
대응된 문자열: 

[^-] 자체가 의미하는 것은 -가 아닌 특정한 하나의 문자를 뜻하기 때문에 5678뒤에는 아무런 문자도 없어서 대응되지 못했다.

활용 1 - 여러 단어가 포함되어 있는지 확인

다음 처럼 영양 성분이 표기되어 있는 문자열의 배열이 있다고 가정해 보자.

스크램블 에그 | 단백질: 10g, 지방: 20g
햄버거 | 단백질: 40g, 탄수화물: 50g, 지방: 30g

문자열에 3대 영양소인 단백질, 탄수화물, 지방이라는 단어가 포함되어있는 행을 찾아야 하는 상황이라면 탐색을 활용해서 쉽게 해결할 수 있다.

표현식 : /(?=.*단백질)(?=.*지방)(?=.*탄수화물).*/gm
대응된 문자열 : 햄버거 | 단백질: 40g, 탄수화물: 50g, 지방: 30g

활용 2 - 특정 패턴으로 시작하지(끝나지) 않는 문자열

유저로부터 입력 받은 url에 프로토콜이 포함되지 않을 경우 통과하는 표현식을 작성해 보자.
부정형 후행 탐색을 사용해야 될 것 같다는 느낌이 강하게 들기 마련이기 때문에 다음과 같이 표현식을 만들게 된다.

표현식 : (?<![a-z]+:\/\/)[a-z.]+
대상 : http://www.google.com
대응된 문자열 : http

예상치 못했던 결과이다. ://로 시작하지 않는 문자열을 찾고자 했지만 http의 경우에는 앞에 아무런 문자열도 없기 때문에 대응되어 버린다. 그렇다면 방향을 틀어서 xxx://로 시작하지 않는 문자열을 탐색하는 표현식을 만들어보자.

표현식 : ^(?![a-z]*:\/\/)[a-z.]+
대상 : http://www.google.com
대응된 문자열 : 
대상 : www.google.com
대응된 문자열 : www.google.com

일단 시작 부분에 프로토콜이 있으면 안되므로 시작을 뜻하는 ^를 넣어준다. 그런 뒤 전방 부정 탐색을 이용해서 프로토콜에 해당하는 문자열이 없는지 탐색한다. 탐색에 해당하는 표현식이 ^바로 뒤에 있으므로 프로토콜에 해당하는 문자열이 탐색된다면 대응되는 문자열이 발생할 수가 없다.

전방 탐색이지만 앞에 탐색할 표현식이 없으므로 탐색 키워드에 매칭되는 문자열이 있는지 없는지만 판단하는 용도로 사용된다. 이러한 특징을 이용하면 문자열에 원하는 대상이 있는지 없는지 판별할 수 있기 때문에 매우 용이하다.

활용 3 - 0이 아닌 실수

실수를 나타내는 정규표현식은 다음과 같다.

^[-]?\d+(\.\d+)?$

프로그래밍언어였다면 대상이 0인지 비교해서 return false를 해도 되겠지만 순수하게 정규표현식만으로 해결하기 위해서 조건을 추가했다.

  • 조건1: 소숫점이 없는 경우에는 0으로 시작하면 안된다.
  • 조건2: 소숫점이 있는 경우에는 0으로 시작해도 되지만 0으로 끝나서는 안된다.

그래서 위에 작성했던 표현식을 소숫점을 포함하는 경우와 그렇지 않은 경우로 분리한다.

^[-]?(\d+|\d+(\.\d+))$

그런 다음 활용2 처럼 부정 탐색을 활용한다.

^[-]?(((?!0)\d+)|(\d+(\.\d+)(?<!0)))$

마지막으로 소숫점 앞자리가 한자리 이상일 경우에만 0이 가능하도록 변경한다.

^[-]?(((?!0)\d+)|(((?!0)\d+|0)(\.\d+)(?<!0)))$

결론

  1. 탐색은 유용하다
  2. 탐색할 방향에 표현식이 없으면 탐색 키워드가 있는지 없는지 판별한다.
  3. 정규표현식은 어렵다ㅜ
profile
기록하는 습관

0개의 댓글