Transcript RegEx

Regular Expression
2011-03-26
봄싹 스터디 http://www.springsprout.org
발표자 : 강희석
1장 정규 표현식 소개(1)
책 소개
 http://www.yes24.com
 ☞ 표현식으로 검색
 책 본문은 103 페이지. 나머지는 부록(60페이지)
 펄은 정규식 언어의 할아버지 격인 랭귀지.
1장 정규 표현식 소개(2)
정규 표현식을 시험해 볼 수 있는 툴
 http://www.forta.com/books/0672325667/
 ☞ Appendix C: The Regular Expression Tester
 ☞ JavaScript, JSP
 ☞ 『손에 잡히는 정규 표현식』 (p157)
 EditPad Pro 에디터
정규 표현식을
가장 잘 지원하는
에디터
 http://www.editpadpro.com/download.html
 http://weitz.de/files/regex-coach.exe
2장 문자 하나 찾기 (1)
메타 문자
문자 그대로 사용되지 않고 특별한 의미를 지니는 문자
escape(이스케이프)
메타 문자 앞에 역슬래시(\)를 붙여서 문자 그대로 해석되게 한다.
예) \. \\ \? \* \+ \^ \$
혹은 일반 문자 앞에 역슬래시(\)를 붙여서 특수한 의미를 지니게 할 수도
있다.
예) \n \t \r
3장 문자 집합으로 찾기 (1)
.
어떤 문자든지 문자 하나와 일치
|
왼쪽 혹은 오른쪽과 일치
[]
문자 집합 구성원 중 하나와 일치
[^]
문자 집합 구성원을 제외하고 일치
-
범위 정의([A-Z]와 같은 형태)
\
다음에 오는 문자를 이스케이프
[Tip]
마침표(.)는 줄바꿈(newline) 문자를 제외한 모든 문자와 일치한다.
혼합 문자 집합인 [\d\D]는 모든 숫자 또는 숫자가 아니니 모든 것을
의미합니다. 즉 모든 문자를 의미합니다. 이것은 줄바꿈 문자를 제외한
모든 문자를 일치시키는 .와는 반대로 줄바꿈 문자까지도 포함한 모든
문자를 일치시키는 일반적인 방법입니다.
3장 문자 집합으로 찾기 (2)
교대(alternation)
이 용법에서 종종 'or'라고 발음하는 파이프 기호(|)는 왼쪽이나 오른쪽과
일치할 수 있음을 의미합니다. 즉 파이프 기호 왼쪽의 패턴이 일치하지
않으면 오른쪽 패턴이 일치를 시도할 기회를 얻습니다.
/fred( |\t)+barney/ - 빈 칸이나 탭 문자와 일치. 두 이름 사이에 빈
칸이나 탭 문자가 적어도 하나는 존재해야 합니다.
/fred( +|\t+)barney/ - fred 와 barney 사이의 문자가 모두 동일한 문자로
구성되기를 원한다면. 구분자는 모두 빈 칸이거나 모두 탭이어야 함.
/fred (and|or) barney/ - fred and barney 혹은 fred or barney
문자 집합(character class)
대괄호 안에 들어갈 수 있는 문자 목록으로 집합 안에 있는 모든 단일
문자와 일치합니다.
단지 하나의 단일 문자와 일치하지만 일치하는 문자는 목록에 있는 문자 중
하나입니다.
3장 문자 집합으로 찾기 (3)
하이픈(-)
대괄호([]) 안에서만 메타 문자인 특수한 메타 문자다. 집합 밖에서
하이픈(-)은 단순히 문자 그대로 하이픈(-)과 일치한다. 그래서 집합
밖에서는 하이픈(-) 문자에 굳이 역슬래시(\)를 붙일 필요가 없다.
캐럿(^)
대괄호([]) 안에서 이 문자 바로 뒤에 있는 문자나 범위 뿐만 아니라 집합
안에 있는 문자나 범위를 모두 제외한다.
대괄호([]) 밖에서는 줄의 처음을 나타내는 메타 문지이다.
4장 메타 문자 사용하기 (1)
공백 메타 문자
메타 문자
[\b]
설명
역스페이스
\f
페이지 넘김(form feed)
\n
줄바꿈
\r
캐리지 리턴
\t
탭
\v
수직 탭
숫자 메타 문자
메타 문자
설명
\d
숫자 하나([0-9]와 같다.
\D
숫자를 제외한 문자 하나
([^0-9]와 같다)
4장 메타 문자 사용하기 (3)
포직스(POSIX) 문자 클래스 사용하기
분류
내용
[:alnum:]
모든 영숫자([a-zA-Z0-9]와 같다)
[:alpha:]
모든 영문자([a-zA-Z]와 같다)
[:blank:]
빈칸(space)이나 탭 문자([\t ]와 같다)
[:cntrl:]
아스키 제어문자(아스키 0번부터 31, 127번)
[:digit:]
모든 한 자리 숫자([0-9]와 같다)
[:graph:]
[:print:]와 동일하나 빈칸(space)은 제외
[:lower:]
모든 소문자([a-z]와 같다)
[:print:]
출력 가능한 문자
[:punct:]
[:alnum:]이나 [:cntrl:]가 포함되지 않은 모든 문자
[:space:]
빈칸을 포함한 모든 공백 문자([\f\n\r\t\v ]와 같다)
[:upper:]
모든 대문자([A-Z]와 같다)
[:xdigit:]
모든 16진수 숫자([a-fA-F0-9]와 같다)
5장 반복 찾기 (1)
수량자(Quantifier)
수량자
내용
*
문자가 없는 경우나 하나 이상 연속하는 문자 찾기
*?
게으른 * 문자
+
문자 하나 이상 찾기
+?
게으른 + 문자
?
문자가 없거나 하나인 문자 찾기
{n}
정확히 요소와 n번 일치
{m,n}
요소와 m에서 n번 일치
{n,}
요소와 n번 이상 일치
{n,}?
게으른 {n,}
5장 반복 찾기 (2)
탐욕적(greedy) 수량자와 게으른(lazy) 수량자
탐욕적 수량자
게으른 수량자
*
*?
+
+?
{n,}
{n,}?
5장 반복 찾기 (3)
[목표] <B> 태그로 둘러싸인 텍스트를 구한다.
[예문]
This offer is not available to customers
living in <B>AK</B> and <B>HI</B>.
[정규 표현식] <[Bb]>.*</[Bb]>
[결과]
This offer is not available to customers
living in <B>AK</B> and <B>HI</B>.
[정규 표현식] <[Bb]>.*?</[Bb]>
[결과]
This offer is not available to customers
living in <B>AK</B> and <B>HI</B>.
우리가 원하는
텍스트를 포함하긴
했지만, 찾으려 하지
않은 텍스트도
포함됐다.
6장 위치 찾기 (1)
수량자
내용
^
문자열의 시작과 일치
\A
문자열의 시작과 일치
$
문자열의 끝과 일치
\Z
문자열의 끝과 일치
\<
단어의 시작과 일치
\>
단어의 끝과 일치
\b
단어 경계와 일치
\B
\b와 반대로 일치
6장 위치 찾기 (2)
앵커
기본적으로 문자열의 시작 지점에서 패턴이 일치하지 않으면 패턴은
문자열에서 '떠올라서' 다른 부분을 일치시키기 위해 움직입니다. 하지만
패턴이 움직이지 않도록 문자열의 특정 부분에 묶어두는 여러 종류의
앵커가 있습니다.
캐럿 앵커(^) 달러 기호($),
캐럿 앵커(^)는 문자열의 시작 지점을 표시하며 달러 기호($)는 문자열의
끝 지점을 표시합니다. 그러므로 /^fred/ 패턴은 문자열의 첫 부분에
fred가 나올 때만 일치하기 때문에 manfred mann 같은 문자열과는 일치하지
않습니다. 그리고 /rock$/은 문자열의 끝 부분에 rock이 나올 때만
일치하므로 knute rockne 같은 문자열과는 일치하지 않습니다.
/^\s*$/ - 빈 줄을 일치시키는 패턴
6장 위치 찾기 (3)
단어 앵커
문자열의 끄트머리에만 앵커가 있는 것은 아닙니다. 단어 경계 앵커인 \b는
단어의 끄트머리에 일치합니다. 그러므로 /\bfred\b/를 사용하면 단어
fred를 일치시킬 수 있지만 frederick 이나 alfred, manfred mann은
일치하지 않습니다. 이 앵커는 워드프로세서의 검색 명령에서 자주 볼 수
있는 '전체 단어에만 일치(match whole words only')류의 기능과
비슷합니다.
안타깝게도 단어 경계 앵커가 사용하는 단어의 의미는 여러분이나 우리가
생각하는 것과 좀 다릅니다. 여기서 말하는 단어는 평범한 문자와 숫자,
밑줄로 구성된 \w 유형의 단어입니다. 그러므로 \b 앵커는 \w 문자 집합의
시작과 끝 부분에 일치합니다.
\B - 단어 경계 앵커의 반대 역할을 합니다.
그래서 \B는 \b가 일치하지 않는 지점과 일치합니다.
/\bsearch\B/ 패턴은 searches과 searching, searched는 일치하지만
search나 researching과는 일치하지 않습니다.
7장 하위 표현식 사용하기 (1)
하위 표현식으로 묶기
[목표] HTML 페이지에서 &nbsp; 가 2개 이상 반복되는 곳 찾기
[예문]
Hello, my name is Ben&nbsp;Forta, and I am
the author of books on SQL, ColdFusion, WAP,
windows&nbsp;&nbsp;2000, and other subjects.
[정규 표현식] &nbsp;{2,}
[결과]
일치하는 곳 없음. 왜???
7장 하위 표현식 사용하기 (2)
[목표] IP주소 찾기
[예문]
Pinging hog.forta.com [12.159.46.200]
with 32 bytes of data:
Wrong IP address: 31.299.46.200
[정규 표현식] \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
[결과]
Pinging hog.forta.com [12.159.46.200]
with 32 bytes of data:
Wrong IP address: 31.299.46.200
[정규 표현식 2] (\d{1,3}\.){3}\d{1,3}
어? 잘못된 IP
주소와도 일치하네?
7장 하위 표현식 사용하기 (3)
[목표] IP주소 찾기
[IP주소를 구성하는 각 숫자 묶음을 유효한 조합으로 정의하는 규칙]
1. 모든 한 자리 혹은 두 자리 숫자
2. 1로 시작하는 모든 세 자리 숫자
3. 2로 시작하면서 두 번째 자리 숫자가 0부터 4사이의 모든 세 자리 숫자
4. 25로 시작하면서 세 번째 자리 숫자가 0부터 5사이의 모든 세 자리 숫자
[정규 표현식]
(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[05]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))
[결과]
Pinging hog.forta.com [12.159.46.200]
with 32 bytes of data:
Wrong IP address: 31.299.46.200
7장 하위 표현식 사용하기 (4)
[목표] 1900년도와 2000년도 4자리 연도 구하기
[예문]
ID:042
SEX: M
DOB: 1967-08-17
Status: Active
[정규 표현식]
19|20\d{2}
[결과]
ID:042
SEX: M
DOB: 1967-08-17
Status: Active
원하는 연도를 제대로
찾지 못함.
8장 역참조 사용하기 (1)
[목표] 같은 단어를 2개 입력한 오자를 모두 찾고 싶다.
[예문]
This is a block of of text,
serveral words here are are
repeated, and and they
should not be.
[정규 표현식]
[ ]+(\w+)[ ]+\1
[결과]
This is a block of of text,
serveral words here are are
repeated, and and they
should not be.
8장 역참조 사용하기 (2)
[목표] HTML 헤더 태그를 모두 찾고 싶다.
[예문]
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>Coldfusion</H2>
[정규 표현식]
<[hH]([1-6])>.*?</[hH]\1>
[결과]
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>Coldfusion</H2>
9장 전방탐색과 후방탐색(1)
 전방탐색은 모든 주요 정규 표현식 구현에서 지원하지만,
후방탐색은 광범위하게 지원하지 않는다.
 자바, .NET, PHP, 펄에서는 몇 가지 제약이 있지만 후방탐
색을 지원한다. 자바스크립트와 콜드퓨전에서는 지원하
지 않는다.
전방탐색 - 앞으로 찾기
 전방탐색(lookahead) 패턴은 일치 영역을 발견해도 그 값
을 반환하지 않는 패턴.
 전방탐색은 실제로는 하위 표현식이며, 하위 표현식과 같
은 형식으로 작성한다.
 전방탐색 패턴의 구문은 ?=로 시작하고 등호(=) 다음에
일치할 텍스트가 오는 하위 표현식이다.
9장 전방탐색과 후방탐색(2)
소비한다. (consume)
일부 정규 표현식 문서에서는 일치하는 영역을 반환하는 동작을 표현할 때
'소비한다.(consume)'라는 용어를 쓴다. 이럴 경우 전방탐색은 '소비하지
않는다(not consume)'고 말한다.
[목표] 본문에는 URL 목록이 있고, 각 URL에서 프로토콜의 위치를 추출해야
한다.
[예문]
http://www.forta.com/
https://mail.forta.com/
전방탐색 메타 문자를
ftp://ftp.forta.com/
빼면? .+(:)
[정규 표현식] .+(?=:)
[결과]
http://www.forta.com/
https://mail.forta.com/
ftp://ftp.forta.com/
9장 전방탐색과 후방탐색(3)
후방탐색 - 뒤로 찾기
앞서 본 것처럼, ?=는 앞으로 탐색한다.
일치하는 텍스트 다음에 무엇이 오는지 찾고, 발견한 텍스트 자체는
소비하지(consume) 않는다.
따라서 ?=는 전방 탐색 연산자라고 부른다.
많은 정규 표현식 구현에서는 전방탐색과 더불어
후방탐색(lookbehind) 기능도 지원한다.
텍스트를 반환하기 전에 탐색하는 작업은 뒤쪽을 탐색하기도
포함한다. 후방탐색 연산은 ?<=이다.
9장 전방탐색과 후방탐색(4)
[목표] 데이터베이스 제품 목록에서 가격만 필요할 경우
[예문]
ABC01: $23.45
HGG42: $5.31
CFMX1: $899.00
XTC99: $69.96
Total items found: 4
[정규 표현식] \$[0-9.]+
[결과]
ABC01: $23.45
HGG42: $5.31
CFMX1: $899.00
XTC99: $69.96
Total items found: 4
달러 기호($)가
필요없다면?
9장 전방탐색과 후방탐색(5)
[정규 표현식] [0-9.]+
불필요한 숫자까지
같이 검색되네…
[결과]
ABC01: $23.45
HGG42: $5.31
CFMX1: $899.00
XTC99: $69.96
Total items found: 4
해결책은…?
9장 전방탐색과 후방탐색(6)
[정규 표현식] (?<=\$)[0-9.]+
[결과]
ABC01: $23.45
HGG42: $5.31
CFMX1: $899.00
XTC99: $69.96
Total items found: 4
[분석]
제대로 동작한다.
(?<=\$) 는 달러 기호($)와 일치하지만 소비하지는 않고, 단지 앞에 달러
기호($)가 없는 가격만 반환한다.
[주의]
전방탐색 패턴은 마침표(.)와 더하기(+)를 포함하여 텍스트의 길이를 다양하게
일치시킬 수 있으며, 매우 동적이다. 반대로 후방탐색 패턴은 보통 일치시킬
텍스트의 길이를 고정해야 한다. 거의 모든 정규 표현식 구현에는 이런 제약이
있다.
9장 전방탐색과 후방탐색(7)
전방탐색과 후방탐색 함께 사용하기
[목표] HTML 페이지에서 제목(TITLE) 찾기
[예문]
<HEAD>
<TITLE>Ben Forta's Homepage</TITLE>
</HEAD>
[정규 표현식] (?<=<[tT][iI][tT][lL][eE]>).*(?=</[tT][iI][tT][lL][eE]>)
[결과]
<HEAD>
<TITLE>Ben Forta's Homepage</TITLE>
</HEAD>
9장 전방탐색과 후방탐색(8)
[분석]
(?<=<[tT][iI][tT][lL][eE]>)는 후방탐색 작업으로 <TITLE>과 일치하며,
소비하지는 않는다. (?=</[tT][iI][tT][lL][eE]>)도 같은 방식으로
</TITLE>과 일치하며, 역시 소비하지는 않는다. 오직 제목 텍스트만 반환한다.
[Tip]
이 예제에서 혼란을 방지하고자 첫 번째로 찾을 문자인 <를 이스케이프 하는
편이 바람직할 수도 있다. 즉(?<=< 대신 (?<=\<로 써도 된다.
9장 전방탐색과 후방탐색(9)
부정형 전후방 탐색
 지금까지 살펴봤듯이 후방탐색과 전방탐색은 반환할 텍스트의
위치 즉, 찾고자 하는 부분의 앞뒤를 특별히 지정하고 싶을 때
주로 사용한다. 이런 방법들을 긍정형 전방탐색과 긍정형 후방
탐색이라고 한다. 긍정(positive)이라는 말을 쓰는 이유는 실제
로 일치하는 텍스트를 찾기 때문이다.
 전후방 탐색 중에서 부정형(negative) 전후방탐색은 비교적 덜
쓰는 방법이다. 부정형 전방탐색은 앞쪽에서 지정한 패턴과 일
치하지 않는 텍스트를 찾고, 부정형 후방탐색도 이와 비슷하게,
뒤쪽에서 지정한 패턴과 일치하지 않는 텍스트를 찾는다.
 전후방 탐색 명령에서 부정형을 나타낼 때는 등호(=) 대신 느
낌표(?)를 사용한다.
9장 전방탐색과 후방탐색(10)
종류
설명
(?=)
긍정형 전방탐색
(?!)
부정형 전방탐색
(?<=)
긍정형 후방탐색
(?<!)
부정형 후방탐색
[Tip]
대개 전방탐색을 지원하는 정규 표현식 구현은 긍정형과 부정형 전방탐색을
모두 지원한다. 마찬가지로 후방탐색을 지원하는 구현에서도 긍정형과
부정형 후방탐색을 모두 지원한다.
9장 전방탐색과 후방탐색(11)
[목표] 예문에서 가격만 찾는다.
[예문]
I paid $30 for 100 apples,
50 oranges, and 60 pears.
I saved $5 on this order.
[정규 표현식] (?<=\$)\d+
[결과]
I paid $30 for 100 apples,
50 oranges, and 60 pears.
I saved $5 on this order.
9장 전방탐색과 후방탐색(12)
[목표] 이번에는 반대로 가격이 아니라 수량만 찾는다.
[정규 표현식] \b(?<!\$)\d+\b
\b 는 왜 붙은 걸까?
[결과]
I paid $30 for 100 apples,
50 oranges, and 60 pears.
I saved $5 on this order.
[분석]
표현식(?<!\$)는 부정형 후방탐색으로, 앞에 달러 기호($)가 없는 숫자와만
일치한다.
9장 전방탐색과 후방탐색(13)
[정규 표현식] (?<!\$)\d+
[결과]
I paid $30 for 100 apples,
50 oranges, and 60 pears.
I saved $5 on this order.
[분석]
단어 경계가 없으면, $30에 있는 0도 일치한다. 왜 그럴까?
숫자 0 앞에 달러 기호($)가 없기 때문이다.
전체 패턴을 단어 경계로 감싸 이 문제를 해결했다.
9장 전방탐색과 후방탐색(14)
[정리해 보자]
 전방탐색과 후방탐색을 사용하면, 무엇을 더 반환할




지 더 구체적으로 명시할 수 있다.
전후방 탐색 명령어는 하위 표현식을 통해 텍스트의
위치를 지정하지만, 소비하지는 않는다.
일치하지만, 일치한 텍스트 자체에는 포함하지 않는
다는 이야기다.
긍정형 전방탐색은 (?=)로 정의하고 부정형 전방탐
색은 (?!)로 정의한다.
몇몇 정규 표현식 구현에서는 (?<=)로 긍정형 후방탐
색을 지원하고, (?<!)로 부정형 후방탐색을 지원한다.
10장 조건 달기(1)
 드물게 쓰는 기능이기는 하지만, 정규 표현식은 내에
조건 처리를 포함시킬 수 있다.
왜 조건을 다는가?
 (123)456-7890 과 123-456-7890 은 모두 올바른 미국 전
화번호 형식이다.
 1234567890, (123)-456-7890, (123-456-7890 은 숫자의
수는 맞지만, 형식이 바르지 않다.
 정규 표현식을 어떻게 작성해야 올바른 형식만 찾을 수
있을까?
10장 조건 달기(2)
[예문]
123-456-7890
(123)456-7890
(123)-456-7890
(123-456-7890
1234567890
123 456 7890
( 나 ) 는 정규식에서
하위 표현식을
의미하므로 문자 그대로
처리하기 위해서는
escape 해야 한다.
[정규 표현식] \(?\d{3}\)?-?\d{3}-\d{4}
[결과]
123-456-7890
(123)456-7890
(123)-456-7890
(123-456-7890
1234567890
123 456 7890
잘못된 형식을 걸러낼
수 없네...
10장 조건 달기(3)
[분석]
\(?는 여는 괄호가 없거나 하나인 경우 일치한다. 이때 여는 괄호(()를
이스케이프 해야 함을 주목하자.
\d{3}는 처음에 나오는 세 자리 숫자와 일치하며, \)?는 닫는 괄호가
없거나 하나인 경우 일치한다.
그리고 -?는 하이픈이 없거나 하나인 경우 일치하며, \d{3}-\d{4}는
하이픈으로 구분된 나머지 일곱 숫자와 일치한다.
이 패턴은 확실히 마지막 두 행과는 일치하지 않는다. 하지만 세 번째와 네
번째 행과는 일치하는데, 두 행 모두 바른 형식이 아니다.
세 번째 전화번호에는 닫는 괄호())와 하이픈(-)이 둘 다 있고, 네
번째에는 괄호의 짝이 맞지 않는다.
\)?-?을 [\)-]?로 바꾸면, 오직 닫는 괄호())나 하이픈(-) 가운데 하나만
일치하여 세 번째 줄을 제거할 수 있지만, 네 번째 줄은 제거할 수 없다.
이 패턴은 여는 괄호(()가 있을 때만 닫는 괄호())와 일치해야 한다. 괄호
한 쌍이 없다면, 하이픈(-)을 찾아야 하는데, 이런 패턴은 조건 처리를
쓰지 않고는 구현할 수 없다.
10장 조건 달기(4)
정규 표현식에서 조건을 나타내는 문법
 역참조 조건 문법
 (?(backreference)true)
 ☞ 물음표로 조건을 시작하고, 괄호 안에 역참조를 지정한 다음, 역참조가 존재하는
경우에만 평가될 표현식이 바로 뒤에 나온다.
 (?(backreference)true|false)
 ☞ else 표현식. 역참조가 존재하지 않는 경우만 수행되는 표현식.
 전후방 탐색 조건 문법
 전후방 탐색 조건 문법은 역참조(괄호 안에 넣는 숫자)가 완전히 전후방 탐색 표현식
으로 대체되었다는 점만 빼고는 역참조 조건과 동일하다.
10장 조건 달기(5)
[주의] 모든 정규 표현식 구현에서 조건 처리를 지원하지는 않는다.
[테스트 결과] 자바 JDK 1.5, JDK 1.6,
JavaScript(IE8,IE9,Chrome,FireFox)는 정규식의 '조건'을 지원하지 않는다.
[조건을 이용한 정규 표현식] (\()?\d{3}(?(1)\)|-)\d{3}-\d{4}
[결과]
123-456-7890
(123)456-7890
(123)-456-7890
(123-456-7890
1234567890
123 456 7890
[분석]
(123-456-7890 도 찾음.
이론과 현실의 괴리?
10장 조건 달기(6)
[나의 분석]
정규식은 어떻게든 최대한 매칭시키려고 시도함.
☞ 책이 잘못 됐다(?)
[내가 만든 정규 표현식]
(\(\d{3}\)|(^|(?<=\s))\d{3}-)\d{3}-\d{4}
[결과]
123-456-7890
(123)456-7890
(123)-456-7890
(123-456-7890
1234567890
123 456 7890
이거 생각하느라
2시간 넘게
걸렸음.
☞ 생산성이
떨어진다.
☞계속 연습하면
익숙해진다.
10장 조건 달기(7)
[실패한 나의 정규 표현식]
((?(?<=\()\())\d{3}(?(1)\)|-)\d{3}-\d{4}
☞ 아무 것도 찾지 못함.
후방 탐색 조건은 전방 탐색 조건과는 달리 탐색하고 나서 탐색한 문자를 다시
포함시켜 반환하지 못한다. 또한 전방 탐색 조건이나 후방 탐색 조건을 하위
표현식으로 묶어서 검사하는 역참조 조건은 항상 true로 판단함.
따라서 후방 탐색 조건과 역참조 조건을 결합한 위 표현식으로는 원하는
텍스트를 찾을 수 없다.
[전방 탐색 조건 테스트]
\d{3}((?(?=-)))(?(1)-7890)
[후방 탐색 조건 테스트]
((?(?<=-)))\d{3}(?(1)-7890)
[결론] 전후방 탐색 조건
전체를 하위 표현식으로
묶은 것을 역참조 한 것은
언제나 true라고 판단한다.
10장 조건 달기(8)
이렇게 해야지 탐색 결과의
참,거짓을 제대로 역참조
할 수 있다.
[전방 탐색 조건 테스트]
\d{3}(?(?=-)())(?(1)-7890)
[후방 탐색 조건 테스트]
(?(?<=-)())\d{3}(?(1)-7890)
[실패한 나의 정규 표현식 2]
(?(?<=(\()))\d{3}(?(1)\)|-)\d{3}-\d{4}
[결과]
123-456-7890
(123)456-7890
((123)456-7890
(123)-456-7890
(123-456-7890
1234567890
123 456 7890
후방 탐색 조건이 패턴의 맨 앞에
왔을 때 전방 탐색 조건과는 달리
탐색하고 나서 탐색한 문자를
다시 포함시켜 반환하지 못한다.
10장 조건 달기(9)
전방 탐색과 후방 탐색의 차이
[목표] 123-456 을 전방 탐색과 후방 탐색으로 찾는다.
[예문]
123456
123--456
123-456-7890
(123)456-7890
(123)-456-7890
(123-456-7890
1234567890
123 456 7890
[전방 탐색 정규 표현식] 123(?=-)-456 ☞ 조건식의 오른쪽에서 탐색한다.
[후방 탐색 정규 표현식] 123-(?<=-)456 ☞ 조건식의 왼쪽에서 탐색한다.
10장 조건 달기(10)
전방 탐색 조건과 후방 탐색 조건의 차이
[목표] 123456 혹은 123-456 을 전방 탐색 조건과 후방 탐색 조건으로
찾는다.
[예문]
전방 탐색의 경우 탐색하는
123456
위치가 조건과 오른쪽 값 사이.
123--456
후방 탐색의 경우 탐색하는
123-456-7890
위치가 조건의 왼쪽 값이고,
(123)456-7890
조건의 왼쪽 값에 따라
(123)-456-7890
참,거짓이 결정된다.
(123-456-7890
1234567890
123 456 7890
[전방 탐색 조건 정규 표현식] 123(?(?=-)-)456
[후방 탐색 조건 정규 표현식] 123-(?(?<=-))456
☞ 123-456 만 찾음. ☞후방 탐색 조건식이 패턴 가운데에 오는 경우 예문에
따라 조건의 참,거짓이 결정되는 것이 아니라 조건의 왼쪽 패턴에 따라
결정된다.
10장 조건 달기(11)
후방 탐색 조건이 제약
사항이 있다면 전방 탐색
조건으로 바꿔 보자.
[나의 정규 표현식 3]
(^|\s)(?(?=(\())\()\d{3}(?(2)\)|-)\d{3}-\d{4}
[결과]
123-456-7890
(123)456-7890
((123)456-7890
(123)-456-7890
(123-456-7890
1234567890
123 456 7890
10장 조건 달기(12)
책이 정말 잘못된 것일까?
조건 달기를 이용해서 좀
더 간단히 정규 표현식을
만들수 없을까?
10장 조건 달기(13)
[책에서 제시한 정규 표현식] (\()?\d{3}(?(1)\)|-)\d{3}-\d{4}
책에서 제시한 표현식은
방법은 맞다. 하위표현식
뒤에 ?을 놓고 역참조로
검사하는 방식.
(123-456-7890
그런데 이것을 거르지 못한
이유는?
표현식 앞뒤 경계를 걸러줄
표현식이 더 필요하다.
10장 조건 달기(14)
[방법1] ([^(]|^)(\()?\d{3}(?(2)\)|-)\d{3}-\d{4}
이 방법을 쓰면
(123-456-7890)
도 찾지 못함.
이것이 원하는 것인가?
만약에 (123-456-7890)은
맞다고 할 것인가?
그렇다면...
10장 조건 달기(15)
[방법2] (\()?(\()?\d{3}(?(2)\)|-)\d{3}-\d{4}(?(1)\))
이렇게?
(123-456-7890 도
찾음.'123-456-7890'과
매치.
예전과 마찬가지로 경계의
문제가 있다.
[방법3] ([^(]|^)(\()?(\()?\d{3}(?(3)\)|-)\d{3}-\d{4}(?(2)\))
10장 조건 달기(16)
역참조 조건을 이용한 예제 하나 더
역참조 조건은 이전 하위 표현식이 검색에 성공했을 경우에만 다시
그 표현식을 검사한다.
(?(backreference)true)
[목표]
본문 안에 있는 <IMG> 태그를 모두 찾아야 하고, 링크 태크 <A>와
</A>로 감싸져 있을 때는 이 링크 태그까지 일치시키기.
10장 조건 달기(17)
[예문]
<!-- Nav bar -->
</TD>
<A HREF="/home"><IMG SRC="/images/home.gif"></A>
<IMG SRC="/images/spacer.gif">
<A HREF="/search"><IMG SRC="/images/search.gif"></A>
<IMG SRC="/images/spacer.gif">
<A HREF="/help"><IMG SRC="/images/help.gif"></A>
</TD>
[정규 표현식]
(<[Aa]\s+[^>]+>\s*)?<[Ii][Mm][Gg]\s+[^>]+>(?(1)\s*</[Aa]>)
10장 조건 달기(18)
[결과]
<!-- Nav bar -->
</TD>
<A HREF="/home"><IMG SRC="/images/home.gif"></A>
<IMG SRC="/images/spacer.gif">
<A HREF="/search"><IMG SRC="/images/search.gif"></A>
<IMG SRC="/images/spacer.gif">
<A HREF="/help"><IMG SRC="/images/help.gif"></A>
</TD>
[분석]
(<[Aa]\s+[^>]+>\s*)? 는 <A>나 <a> 시작 태그가 일치한다.
이때 속성이 있다면 속성도 함께 일치하며, 여기서 마지막에 있는 물음표는
이 패턴이 없어도 되고, 있다면 일치함을 의미한다.
(?(1)\s*</Aa]>)는 조건으로 시작하는데, ?(1)은 역참조 1(<A>시작 태그)이
있을 때만 수행하라는 말이다.
다른 말로,태그 <A>와 일치한다면 그 뒤의 종료 태그도 일치시키라는
뜻이다.
만약 (1) 이 있다면, \s*</[Aa]>는 </A>가 나오기 전까지 모든
(공백)문자를 일치 영역에 넣어 준다.
10장 조건 달기(19)
[목표] 올바른 미국 우편번호를 찾는다.
12345 처럼 표현된 다섯 자리 ZIP이거나 12345-6789처럼 표현된 ZIP+4
코드다. 하이픈은 오직 뒤에 숫자 네 개가 더 있을 때만 사용한다.
[예문]
11111
22222
3333344444-4444
[정규 표현식] \d{5}(-\d{4})?
이걸로 충분할까?
10장 조건 달기(20)
[결과]
11111
22222
3333344444-4444
잘못된 형식의
우편번호도 찾았다.
[전방탐색 조건을 이용한 정규 표현식] \d{5}(?(?=-)-\d{4})
[결과]
11111
22222
3333344444-4444
10장 조건 달기(21)
[Tip]
조건을 달면 패턴이 매우 복잡해 보일 수 있고, 그렇기 때문에 문제가
발생했을 때 찾아 해결하기도 힘들다.
그래서 표현식을 작은 조각으로 나누고 그 조각들을 시험해 본 다음에
합치는 방법을 사용하는 편이 좋다.
[Tip]
더 간단한 방법으로 비슷한 결과를 얻을 수 있기 때문에 전후방탐색 조건은
자주 사용하지 않는다.
표현식이 더
[전방탐색 조건을 사용하지 않는 정규 표현식]
길어지지만
(\d{5}-\d{4}\b|\d{5}(\s|$))
알아보기 더
편할수도 있다.
10장 조건 달기(22)
[정리해 보자 ]
조건을 충족하는지 아닌지에 따라 표현식을 수행하도록 정규 표현식 패턴에
조건을 달 수 있다.
조건은 역참조(역참조가 가리키는 하위 표현식의 존재를 확인한다)나
전후방탐색으로 설정할 수 있다.
대다수의 정규식 구현체에서 정규식의 조건 달기를 구현하고 있지 못하므로
전후방 탐색 조건을 사용하지 않는 표현식을 쓸 수 밖에 없는 경우도 있다.
생각해 봅시다 (1)
[목표]
미국 사회보장번호를 찾는다.
숫자 조합 대부분을 사회보장번호로 사용할 수 있지만, 필요하다면 몇 가지
규칙을 사용해 일치되는 범위를 줄일 수 있다.
모든 항목이 0인 사회보장번호는 없으며, 첫 세 자리 숫자는 728보다 크지
않다.(아직 이보다 큰 숫자를 할당하지 않았지만 미래에는 사용하게 될 것이다).
하지만 이 규칙을 패턴으로 작성하려면 매우 복잡하므로, 일반적으로는
간단하게 \d{3}-\d{2}-\d{4}를 사용한다.
이 규칙을 패턴으로
만들어보면?
생각해 봅시다 (2)
[예문]
John Smith: 123-45-6789
000-00-0000
000-00-0009
727-00-0101
728-11-1111
738-00-0000
729-00-0000
888-88-8888
726-11-1111
생각해 봅시다 (3)
[정규 표현식] (?(?=000-00-000)000-00-000[1-9]|([0-7])(?(?<=7)[02]|\d)(?(?<=72)[0-8]|\d)-\d{2}-\d{4})
[분석]
000-00-000 을 전방탐색으로 찾고 있으면 0을 제외하고 1에서 9까지의 수
만 매칭되어야 한다.
000-00-000 을 전방탐색으로 찾아서 없으면
맨 처음 숫자는 0 에서 7까지의 숫자이고,
그 다음 숫자는 그 앞 문자가 7이면 (후방탐색 ?<=) 0에서 2까지의 문자만
유효하고 그 이외의 경우는 모든 숫자(\d)가 올 수 있다.
그 다음 숫자의 경우 그 앞 문자가 72 인 경우 0 에서 8 까지의 숫자만
유효하고 그 이외의 경우는 한 자리 숫자(\d)가 오면 된다.
조건 달기의 경우 대부분의 정규 표현식
구현체에서 지원하고 있지 않음. 이것을
안 쓰고 만들어보면?
생각해 봅시다 (4)
[정규 표현식] (([0-6]\d{2}|7[01]\d|72[0-8])-\d{2}-\d{4})
[분석]
앞의 정규 표현식에서는 후방탐색 조건 문법을 주로 사용하였는데 이 정규
표현식에서는 조건을 만족하는 패턴을 OR(|)로 계속 연결해서 구성하였다.
그런데 000-00-0000 이 제외 안됨.
이것을 가장 간단하게 제외시킬 수 있는 방법은 다음과 같이 맨 앞에 전방
탐색 조건을 한번 사용하면 된다.
[정규 표현식] (?(?=000-00-000)000-00-000[1-9]|(([06]\d{2}|7[01]\d|72[0-8])-\d{2}-\d{4}))
[분석]
전방 탐색 조건을 사용 안하고 다음과 같이 사용하면 000-00-0000 을 제외
시킬 수 없음.
[잘못된 정규 표현식] (000-00-000)?(?(1)[0]|([06]\d{2}|7[01]\d|72[0-8])-\d{2}-\d{4})