그때 그때 생각나는 유용한 정규식을 소개해보려고 한다.

 

오늘은 그 첫 포스팅이다^^ 물론, 이 카테고리는 정규식 기본은 어느 정도 배웠다고 여기고 진행한다.

 

"성이 정이요 이름은 규식씨?"식으로 갸우뚱 하는 분은 정규식 이야기나 삽질중독재활센터 읽고 오시기 바란다.

 

또, 나 역시 정규식은 언제나 배우는 입장이다. 정규식에 정답이란 없다. 정규식은 단순한 skill이 아니라 art라고 했다. 나도 틀릴 수 있고 정규식의 달인이라 해도 틀릴 수 있다. 또, art이기 때문에 그 누구도 정답이라고 우길 수 없다.

 

그냥 모두 알량한 skill을 자랑하지 말고 겸손한 artist가 되면 그만이다. 

 

참 세상 좋아졌다.

 

예전엔 이런 거 생각도 못했다. 특히, 컴퓨터와 너무도 안어울리는 한글 때문에 무쟈게 고생했다.

 

그런데...

 

정규식에 문자집합이라는게 있다.

 

이런거다.

 

[a-z]   : 영어 소문자

[A-Z]  : 영어 대문자

[0-9]   : 숫자

[aeiou]: 모음

 

[] 안에 일치시키려는 문자를 일일이 나열해주거나 범위 지정이 가능할 경우 그 범위만큼만 지정해 주면 된다.

 

이렇게 해도 된다는 말이다.

 

[k-p] : 알파벳에서 k 부터 p 까지만.

[4-8] : 숫자 4에서 8까지만.

 

그러니까, m[aeiou]n 이라고 정규식 패턴을 쓰면 man, mon, min, men, min, mun  식의 단어를 찾아준다. 1[4-7]8 이라고 하면 148, 158, 168, 178 을 찾아주는 식이다.

 

문자집합에서 [a-z][5-8]처럼 범위를 지정하는 경우는 상식적으로 순서가 매겨질때 가능하다. 그런데, 얼마전까지만 해도 당연히 순서가 매겨지는 한글은 범위 지정이 안됐다.

 

그러던 것이 유니코드를 지원하는 정규식 도구들이 등장하면서 한글도 범위지정이 가능해졌다.

 

이딴게 된다.

 

[가-힣] : 한글 한글자면 뭐든지 okay.

 

그래서 최[가-힣]규이라고 하면 최완규도 되고 최상규도 되고 최원규도 되고... 최홍규까지 된다. 자 사이에 어떤 글자가 와도 상관없다는 말이다. 물론, 우리 삼형제 처렴 규자 돌림에 완-상자까지만 있다면 최[완원상]규 라고 하거나 최[사-자]규 처럼 완원상이 포함될 범위를 지정해 주면 된다.

 

한글이 포함된 줄을 찾는 경우도 한글 범위가 지정되는 문자집합이라면 식은죽 먹기다.

 

.*[가-힣]+.* : 앞에 주절주절 영어든 숫자든 한글이든 마구 반복돼도 상관 없고 가운데 한글이 한 글자 이상 반복된 다음 또 무슨 문자가 반복돼도 상관없다.

 

.* 는 어떤 한 문자가 0개 이상 반복돼도 상관없다는 말이라고 했다. (삽질중독재활센터/정규식 이야기 참조) 따라서 그 가운데 [가-힣]+ 라는 것만 신경쓰면 된다. 이 패턴은 한글 한 글자에 해당한다.

 

[ㄱ-ㅎ]이나 [가-하]가 아니냐고? 

 

일단  [ㄱ-ㅎ] 는 한글 한 글자 범위가 아닌 자소 범위이기 때문에 말이 안되고, [가-하]의 경우 한글의 가능한 한 글자가 가 마지막이 아니라 이 마지막이기 때문이다. 더 이상 자세한 한글 문제는 이 글의 스꼬오뿌~ (scope)를 벗어나므로 다른 전문 문서 찾아보기 바란다. 유니코드에서 한글 첫글자는 이고 마지막 글자는 이라는 것만 알아도 짱구 굴리는 호모 사피엔스는 무슨 말인지 알아들을 걸로 믿는다.

 

위 한글 포함 줄 찾기 패턴을 한글 범위가 지원되지 않을 경우 정규식과 비교해 보시라.

 

한글 범위 지정 문자집합이 지원 안될 경우: ^[^a-zA-Z0-9]+$|^[a-zA-Z0-9]+[^a-zA-Z0-9]+$

 

죽음이다.

 

영어->한글 식으로 반복되는 자막을 한글->영어식으로 회화 문서로 만들어보는 정규식도 마찬가지다. 한글 범위 지정 문자집합이 지원되지 않으면 이런 짓을 해야 한다.

 

영문 자막/한글 자막 두줄 찾기 패턴: (^[ a-zA-Z0-9,.!?']+$)\n(^[^a-zA-Z0-9]+$|^[a-zA-Z0-9]+[^a-zA-Z0-9]+$)

 

이걸 찾아서 뒤집는 예를 보여준바 있다.

 

뒤집기 패턴: \2\n\1

 

두번째 줄(\2)과 첫번째 줄(\1)을 뒤바꾸는 패턴이다. 여기서 영문 자막/한글 자막 두줄 찾기 패턴은 한글 범위 지정 문자집합을 활용하면 이렇게 간단히 표현할 수 있다.

 

영문 자막/한글 자막 두줄 찾기 패턴: (^[ a-zA-Z0-9,.!?']+$)\n(.*[가-힣]+.*)

 

자랑스럽게도 대두족장 정규식 편집기는 한글 문자집합 및 범위 지정 지원한다^^

 

하지만...

 

솔직히 고백해서 이 기능은 내가 만든게 아니라 Python 및 그 GUI tool인 wxPython을 만든 개발자들이 Unicode 를 지원하기 위해 뺑이 친 결과다. 이게 바로 오픈소스(OpenSource)의 힘이라고 본 연사 열라 외치지만... 아무도 들어주지 않을 거 뻔하다ㅡ.ㅡ

 

좌우지간... 정규식 문자집합에 한글이 지원되고, 범위 지정까지 지원된다는 것만 알아도 정규식의 빠우어~ 는 따따블이 됐다고 해도 과언이 아니다.

출처 : http://cafe.naver.com/wankyu.cafe

'Etc' 카테고리의 다른 글

Ajax Loading Image 로딩 이미지 Resource  (0) 2011.12.14
무료 관리자 페이지 템플릿 10선  (0) 2011.07.29
RSS 피드 등록 방법  (0) 2011.05.21
인터넷익스플로러 여러버전 동시 사용  (0) 2011.04.18
정규식 강좌  (0) 2011.04.06
javascript에서의 정규식 설명이지만, 쉽고 명쾌하게 잘 돼 있음.
java에서 정규식 문법을 참조할때도 좋을 듯.

======================================================

1. 개념잡기

일반화 시킨 표현. 이것을 정규표현이라고 요약할 수 있을 것 같다.
다음의 과정을 너무 쉽다 생각말고 따라오길 바란다.

- 감잡기

"12354" -> 숫자
"asdfasf" -> 알파벳
두 가지의 간단정규표현을 만들었다. 실생활의 보기와 비추어보자.
"길이가 3인 이름!"
위의 표현은 길이를 표시하는 방법이 없다.
조금 더 발전시켜서 "알파벳{3}"이런식으로 길이를 표현할 수 있도록 한다. 그리고, "알파벳"란 것도 너무 길다 "알" 이라고 한 글자로 표현한다. 그러면 "길이가 3인 이름"은 "알{3}"으로 표시가 가능하다.
길이가 10인 숫자는 "수{10}"
길이가 1인 알파벳이 나오고 그 다음에 길이가 3인 숫자가 나오는 문자열 "알{1}수{3}" 얼핏이나마 감이 올 것이다.
"첫 글자는A, 그 다음은 아무 알파벳 5글자" -> "A알{5}"

- 조금 더

아이디는 대개 첫 글자는 영문이고 두 번째부터는 영문이나 숫자가 온다. 이것을 표현하기 위해선 이것 들 중에 하나란 의미를 갖는 새로운 표현이 필요하다.
"a,b,c,d 중에 하나" -> [abcd]
응용하면,
"알파벳이나, 숫자중 하나" -> [알수]
"[" 안에 있는 문자들의 순서는 의미가 없으며, 그 표현은 (클래스라고 한다.)
결국 한 글자를 말한다.
위에서 말한 "첫 글자는 영문, 두 번째 부터는 영문이나 숫자가 11자"를 표현하면, "알[알수]{11}".
그런데, 실제로 모든 아이디가 12자인 것은 아니다, 대개 4자부터 12자를 지원한다.
새로운 표현이 등장한다. "몇 자부터 몇 자"
"A가 3글자부터 12자" -> "A{3,12}"
"알파벳이나 숫자가 1자부터 100자" -> "[알수]{1,100}"
이제 아이디를 다시 정의하자.
"첫 글자는 영문, 영문이나 숫자가 3자부터 11자" -> "알[알수]{3,11}"

2. 표현식

지금 까지의 규칙에서 설명한 용어를 실제 정규표현에서 사용하는 표현으로 바꾸고, 다른 세부적인 옵션에 대해 알아보자.

\ : 다음의 글자가 특별한 문자임을 나타낸다. 때론, 그 다음 문자 자체를 의미하기도 한다.
보기를 들면, "\n"은 문자"\"과 문자"n" 두 글자와 매치되는 것을 의미하는 것이 아닌, 새줄(New Line)을 의미하며, "\\"은 첫 "\" 다음 문자인 "\" 자체를 의미한다. 즉, "\\"은 "\"과 매칭된다.

^ : 입력문자열의 맨 처음을 의미한다. (맨 첫 글자가 아니라, 맨 처음이란 문맥적 의미를 말한다. 아주 중요하다) 기본적으로 정규표현은 입력 문자열의 한 줄에만 적용된다. 하지만, 옵션에 따라 여러줄에 적용할 수도 있다. 그럴 경우에는 "^"는 "\n" 나 "\r" 다음의 위치를 의미한다.

$ : "^"는 반대로 입력 문자열의 맨 끝을 의미한다. 역시 여러줄에 정규표현이 적용될 경우에는 "\n"이나 "\r"의 앞의 위치를 의미한다.

* : 이 문자 앞의 표현이 0번내지 무한번 반복될 수 있음을 말한다.
보기를 들면, /a*/은 "a", "", "aaaa", "aaaaa"와 매칭된다.
(0번이상은 없어도 된다는 것을 의미한다.)

+ : *와 같지만, 0번이상이 아니라 1번이상이라는 점을 제외하곤 /*/와 같다.

? : 앞의 표현이 0번 또는 1번. /do(es)?/는 "do", "does"와 매칭된다.

{n} : 앞의 표현이 n은 음수가 아닌 정수이어야 하며, 앞의 표현이 n번 매치되는 것을 말한다.

{n,} : 앞의 표현이 n은 음수가 아닌 정수이어야 하며, n번 이상 매치되는 것을 말한다.

{n,m} : 앞의 표현이 n번 이상 부터 m번 이하까지 매칭되는 것을 말하며, /*/는 /{0,}/과 같으며, /+/는 /{1,}/과 /?/는 /{0,1}/으로 표현 가능하다.

. : "\n"을 제외한 한 글자를 뜻한다. 만일 모든 글자를 표현하고 싶다면("\n"마저도 합친) /[.\n]/을 사용하면 된다.

x|y : x 또는 y와 매칭된다. 보기를 들면, /z|food/는 "z" 또는 "food"와 매칭된다. /(z|f)ood/는 "zood" 또는 "food"와 매칭된다.
(참고로 괄호는 묶어준 것 이상의 의미가 있다.)

(패턴) : 해당 패턴과 매칭시키고, 그 부분을 특정 변수에 담는다.
그 변수 이름은 JScript는 $0~$9까지의 변수에 저장이 되고(Perl과 같다.),
VBScript!에서는 SubMatches 컬렉션에 저장된다.
괄호기호 자체와 매치시키고 싶다면? /\(/와 /\)/를 사용한다.

(?:패턴) : 해당 패턴과 매칭은 시키지만, 그 부분을 특정 변수에 담지 않는다. 왜 이게 필요할까?
위의 보기에서 /(z|f)ood/는 "zood" 또는 "food"와 매칭된다고 했는데, 단순히 매칭의 목적으로 사용했지만, "zood"의 경우 "z"가 $0 이란 변수에 저장이 되고 말았다. 이러한 것을 막기 위해서 사용하는 것이 (?:패턴)이다.

(?=패턴) : (?:패턴)과 동일하지만, 패턴과 일치한 부분이후부터 다음 매치가 일어나지 않고 패턴 앞부터 다시 매칭이 진행된다. 즉, 룩업(lookup, lookahead)을 할 뿐이다. /Windows (?=95|98|NT|2000)/ 은 "Windows 2000"의 "Windows" 부분과 매칭이 되며 다음 매칭은 "2000" 다음 부터가 아닌 "Windows" 다음 부터 진행이 된다.

(?!패턴) : (?=패턴)과 반대다. /Windows (?=95|98|NT|2000)/ 은 "Windows 3.1"의 "Windows" 부분과 매칭이 된다.

[xyz] : "["안에 있는 표현중 하나를 의미한다.

[^xyz] : "["안에 있는 표현을 제외한 것중 하나를 의미한다. "[^abc]"는 "plain"의 "p"때문에 매칭된다.

[a-z] : "a"부터 "z" 까지의 문자중 하나

[^a-z] : "a"부터 "z" 까지의 문자를 제외한 하나

\b : 단어의 경계(단어와 공백, "\n", "\r"의 사이)와 매칭된다. 보기를 들면, "er\b"는 "never"와는 매칭되지만, "verb"와는 매칭되지 않는다.

\B : 단어의 경계가 아닌 것과 매칭된다. "er\B"는 "verb"와는 매칭되지만, "never"와는 매칭되지 않는다.

\cx : Ctrl+x 키와 매칭된다. "\cc"는 Ctrl+C와 매칭된다. x의 범위는 [a-zA-Z]이며, 만일 이 이외의 문자를 사용한다면 "\c"는 "c"와 동일하다.

\d : [0-9]와 같다.

\D : [^0-9]와 같다. 참고로 대문자는 소문자의 반대 의미를 갖는다.

\f : 폼피드(form-feed) 문자를 의미하며, "\x0c"와 "\cL"과 동일하다.

\n : 새 줄(newline)를 의미하며, "\x0a"와 "\cJ"와 동일하다.

\r : 캐리지 리턴(carriage return)을 의미하며, "\x0d"와 "\cM"과 동일하다.

\t : 탭. "\x09", "\cI"과 동일

\v : 버티컬 탭. "\x0b", "\cK"과 동일

\s : 화이트스페이스를 의미한다. 화이트스페이스란 공백, 탭, 폼피드, 캐리지리턴등을 의미한다. [ \f\n\r\t\v]과 동일("\f"앞에 공백이 있다. 주의!)

\S : "[^ \f\n\r\t\v]"

\w : "_"를 포함한 일반적인 단어에 사용되는 문자를 말한다. "[A-Za-z0-9_]" 과 동일

\W : "[^A-Za-z0-9_]"

\xn : n은 2자리 16진수이며, 해당 16진수 코드와 매칭된다. "\x412"는 16진수 41은 "A"이기 때문에 "A2"와 매칭된다.

\num : 캡쳐한 매칭을 가리킨다(백레퍼런스, backreference).
"(.)\1"은 연속된 두개의 문자열을 의미한다.
\n : "\1"은 위에서 캡쳐한 매칭(backreference)를 가리킨다고 했는데, 만일 이 패턴앞에 어떠한 n개의 캡쳐한 표현이 있다면 백레퍼런스이지만, 그렇지 않은 경우에는 8진수로 간주하여 해당 코드의 문자와 매칭된다.

\un : n은 4자리 UNICODE 이다. "\u00A9"은 copyright 심볼인 "ⓒ"와 매칭된다.


greedy, non-greedy

? : 앞에서 설명했는데, 왜 또? 라고 생각할 것이다.
?은 문맥에 따라 특별한 의미를 갖는다.
패턴 "o*"는 "foooood"와 매칭된다. 당연하다! 하지만, "f"앞의 "o"와 매칭되는 것이 아니다!! "ooooo"와 매칭된 것이다. 즉, 기본으로 정규표현 매칭은 가장 큰 범위를 선택한다. 이것을 greedy하다고 한다.
하지만, 때론 작은 범위에 매칭시킬 필요가 있을 경우가 있다.
(이의 적절한 보기는 잠시 후에 나온다.) "o*?"가 방금 말한 non-greedy 매칭이다.
수량관련 문자인 "*", "+", "?", "{n}", "{n,}", "{n,m}" 다음에 "?"가 나오면 non-greedy 매칭이된다.
잠시, 위에서 "o*?"가 "o"와 매칭된다고 했는데 이상하게 생각한 분이 있었을 것이다. 맞다. "o*?"는 ""와 매칭되었다. "*"는 0개이상임을 잊어선 안된다. "o+?"가 "o"와 매칭된다.

4. 보기

- 웹 주소

"http://msdn.microsoft.com/scripting/default.htm"
위의 주소를 표현할 수 있는 정규표현은 아래와 같다.
/(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/
$1 : http
$2 : msdn.microsoft.com
$3 : 80
$4 : /scripting/default.htm

- 중복된 단어를 하나로

중복된 영어단어를 하나로 합치기 위해선, 우선 단어를 찾아야한다.
그리고 단어는 앞 뒤가 단어의 경계이어야한다. (말이 참 이상하지만..)
따라서, 아래와 같은 1차 정규표현을 얻을 수 있다.

/\b([a-z]+)\b/

연속해서 동일한 두개의 단어... 앞에서 캡쳐한 표현을 다시 활용하면 된다.
그리고, 단어와 단어 사이엔 화이트스페이스가 있다.

/\b([a-z]+)\s+\1\b/

- HTML 태그 제거

HTML문서에서 태그를 제거한 문서를 추출하고자 한다.
태그는 "<"와 ">"로 감싸여 있다.

/<.*>.*</.*>/

그런데, 위의 정규표현을 HTML문서에 적용하여 해당 패턴을 "", 빈문자열로 바꾸면 문서는 빈 문서가 되고 만다.

<html>
<title>...</title>
<body>
<font>.... </font>
...
</body>
greedy한 매칭이 기본값이라고 위에서 언급을 했다. 따라서, 위의 HTML 문서를 보면, <html>....</body>로 생각할 수 있다.
따라서, 문서 전체가 사라지는 것이다. 이것을 막기 위해선 "*"뒤에 "?"를 추가하면 된다.

/<.*?>.*?</.*?>/

아직 끝나지 않았다. :)

좀더 정제를 한다면, 올바른 HTML 문서는 <태그명>과 </태그명>이 서로 일치한다. 이것도 적용한다면,

/<.(*?)>.(*?)</\1>/

위의 $1에 해당되는 부분을 좀 더 생각해보면, ">"를 제외한 문자로 볼 수 있다. 따라서 최종적으로 아래와 같이 정리된다.

/<(\w+)[^>]*?>(.*?)</\1>/

- URL

/(?:^|")(http|ftp|mailto):(?://)?(\w+(?:[\.:@]\w+)*?)(?:/|@)([^"\?]*?)(?:\?([^\?"]*?))?(?:$|")/

- float 상수

/^(((+|-)?\d+(\.\d*)?)|((+|-)?(\d*\.)?\d+))$/ -1.1 1.1 .9 .8



정규식 구문
정규식은 일반 문자(예: a에서 z)와 메타문자 로 알려진 특수 문자로 구성된 텍스트 패턴입니다. 패턴은 텍스트 본문을 검색할 때 일치하는 문자열을 하나 이상 설명합니다. 정규식은 검색되는 문자열과 일치하는 문자 패턴을 찾는 템플릿의 역할을 합니다.

일반적으로 볼 수 있는 몇 가지 정규식 예는 다음과 같습니다.

JScript VBScript! 검색 /^\[ \t]*$/ "^\[ \t]*$" 빈 줄을 찾습니다.
/\d{2}-\d{5}/ "\d{2}-\d{5}" 2자리, 하이픈 및 5자리로 구성된 ID 번호를 찾습니다.
/<(.*)>.*<\/\1>/ "<(.*)>.*<\/\1>" HTML 태그를 찾습니다.



아래 표는 정규식 컨텍스트에 사용되는 모든 메타문자와 메타문자의 동작을 보여줍니다.

문자 설명 \ 그 다음 문자를 특수 문자, 리터럴, 역참조, 또는 8진수 이스케이프로 표시합니다. 예를 들어, "n"은 문자 "n"을 찾고 "\n"은 줄 바꿈 문자를 찾습니다. "\\" 시퀀스는 "\"를 찾고 "\("는 "("를 찾습니다.
^ 입력 문자열의 시작 위치를 찾습니다. Multiline 속성이 설정되어 있으면 ^는 '\n' 또는 '\r'앞의 위치를 찾습니다.
$ 입력 문자열의 끝 위치를 찾습니다. Multiline 속성이 설정되어 있으면 $는 '\n' 또는 'r'뒤의 위치를 찾습니다.
* 부분식의 선행 문자를 0개 이상 찾습니다. 예를 들어, "zo*"는 "z", "zoo" 등입니다. *는 {0,}와 같습니다.
+ 부분식의 선행 문자를 한 개 이상 찾습니다. 예를 들어, "zo+"는 "zo", "zoo" 등이지만 "z"는 아닙니다. +는 {1,}와 같습니다.
? 부분식의 선행 문자를 0개 또는 한 개 찾습니다. 예를 들어, "do(es)?"는 "do" 또는 "does"의 "do"를 찾습니다. ?는 {0,1}과 같습니다.
{ n } n 은 음이 아닌 정수입니다. 정확히 n 개 찾습니다. 예를 들어, "o{2}"는 "Bob"의 "o"는 찾지 않지만 "food"의 o 두 개는 찾습니다.
{ n ,} n 은 음이 아닌 정수입니다. 정확히 n 개 찾습니다. 예를 들어, "o{2}"는 "Bob"의 "o"는 찾지 않지만 "foooood"의 모든 o는 찾습니다. "o{1,}"는 "o+"와 같고, "o{0,}"는 "o*"와 같습니다.
{ n , m } m 과 n 은 음이 아닌 정수입니다. 여기서 m 은 n 보다 크거나 같습니다. 최소 n 개, 최대 m 개 찾습니다. 예를 들어, "o{1,3}"은 "fooooood"의 처음 세 개의 o를 찾습니다. "o{0,1}"은 "o?"와 같습니다. 쉼표와 숫자 사이에는 공백을 넣을 수 없습니다.
? 이 문자가 다른 한정 부호(*, +, ?, { n }, { n ,}, { n , m })의 바로 뒤에 나올 경우 일치 패턴은 제한적입니다. 기본값인 무제한 패턴은 가능한 많은 문자열을 찾는 데 반해 제한적인 패턴은 가능한 적은 문자열을 찾습니다. 예를 들어, "oooo" 문자열에서 "o+?"는 "o" 한 개만 찾고, "o+"는 모든 "o"를 찾습니다.
. "\n"을 제외한 모든 단일 문자를 찾습니다. "\n"을 포함한 모든 문자를 찾으려면 '[.\n]' 패턴을 사용하십시오.
( pattern ) pattern 을 찾아 검색한 문자열을 캡처합니다. 캡처한 문자열은 VBScript!의 경우 SubMatches 컬렉션, Jscript의 경우 $0 ... $9 속성을 이용하여 결과로 나오는 Matches 컬렉션에서 추출할 수 있습니다. 괄호 문자인 ( )를 찾으려면 "\(" 또는 "\)"를 사용하십시오.
(?: pattern ) pattern 을 찾지만 검색한 문자열을 캡처하지 않습니다. 즉, 검색한 문자열을 나중에 사용할 수 있도록 저장하지 않는 비캡처 검색입니다. 이것은 패턴의 일부를 "or" 문자(|)로 묶을 때 유용합니다. 예를 들어, 'industr(?:y|ies)는 'industry|industries'보다 더 경제적인 식입니다.
(?= pattern ) 포함 예상 검색은 pattern 과 일치하는 문자열이 시작하는 위치에서 검색할 문자열을 찾습니다. 이것은 검색한 문자열을 나중에 사용할 수 있도록 캡처하지 않는 비캡처 검색입니다. 예를 들어, "Windows(?=95|98|NT|2000)"는 "Windows 2000"의 "Windows"는 찾지만 "Windows 3.1"의 "Windows"는 찾지 않습니다. 예상 검색은 검색할 문자열을 찾은 후 예상 검색 문자열을 구성하는 문자 다음부터가 아니라 마지막으로 검색한 문자열 바로 다음부터 찾기 시작합니다.
(?! pattern ) 제외 예상 검색은 pattern 과 일치하지 않는 문자열이 시작하는 위치에서 검색할 문자열을 찾습니다. 이것은 검색한 문자열을 나중에 사용할 수 있도록 캡처하지 않는 비캡처 검색입니다. 예를 들어, "Windows(?!95|98|NT|2000)"는 "Windows 3.1"의 "Windows"는 찾지만 "Windows 2000"의 "Windows"는 찾지 않습니다. 예상 검색은 검색할 문자열을 찾은 후 예상 검색 문자열을 구성하는 문자 다음부터가 아니라 마지막으로 검색한 문자열 바로 다음부터 찾기 시작합니다.
x | y x 또는 y 를 찾습니다. 예를 들어, "z|food"는 "z" 또는 "food"를 찾습니다. "(z|f)ood"는 "zood" 또는 "food"를 찾습니다.
[ xyz ] 문자 집합입니다. 괄호 안의 문자 중 하나를 찾습니다. 예를 들어, "[abc]"는 "plain"의 "a"를 찾습니다.
[^ xyz ] 제외 문자 집합입니다. 괄호 밖의 문자 중 하나를 찾습니다. 예를 들어, "[^abc]"는 "plain"의 "p"를 찾습니다.
[ a-z ] 문자 범위입니다. 지정한 범위 안의 문자를 찾습니다. 예를 들어, "[a-z]"는 "a"부터 "z" 사이의 모든 소문자를 찾습니다.
[^ a-z ] 제외 문자 범위입니다. 지정된 범위 밖의 문자를 찾습니다. 예를 들어, "[^a-z]"는 "a"부터 "z" 사이에 없는 모든 문자를 찾습니다.
\b 단어의 경계, 즉 단어와 공백 사이의 위치를 찾습니다. 예를 들어, "er\b"는 "never"의 "er"는 찾지만 "verb"의 "er"는 찾지 않습니다.
\B 단어의 비경계를 찾습니다. "er\B"는 "verb"의 "er"는 찾지만 "never"의 "er"는 찾지 않습니다.
\c x X 가 나타내는 제어 문자를 찾습니다. 예를 들어, \cM은 Control-M 즉, 캐리지 리턴 문자를 찾습니다. x 값은 A-Z 또는 a-z의 범위 안에 있어야 합니다. 그렇지 않으면 c는 리터럴 "c" 문자로 간주됩니다.
\d 숫자 문자를 찾습니다. [0-9]와 같습니다.
\D 비숫자 문자를 찾습니다. [^0-9]와 같습니다.
\f 폼피드 문자를 찾습니다. \x0c와 \cL과 같습니다.
\n 줄 바꿈 문자를 찾습니다. \x0a와 \cJ와 같습니다.
\r 캐리지 리턴 문자를 찾습니다. \x0d와 \cM과 같습니다.
\s 공백, 탭, 폼피드 등의 공백을 찾습니다. "[ \f\n\r\t\v]"와 같습니다.
\S 공백이 아닌 문자를 찾습니다. "[^ \f\n\r\t\v]"와 같습니다.
\t 탭 문자를 찾습니다. \x09와 \cI와 같습니다.
\v 수직 탭 문자를 찾습니다. \x0b와 \cK와 같습니다.
\w 밑줄을 포함한 모든 단어 문자를 찾습니다. "[A-Za-z0-9_]"와 같습니다.
\W 모든 비단어 문자를 찾습니다. "[^A-Za-z0-9_]"와 같습니다.
\x n n 을 찾습니다. 여기서 n 은 16진수 이스케이프 값입니다. 16진수 이스케이프 값은 정확히 두 자리여야 합니다. 예를 들어, '\x41'은 "A"를 찾고 '\x041'은 '\x04'와 "1"과 같습니다. 정규식에서 ASCII 코드를 사용할 수 있습니다.
\ num num 을 찾습니다. 여기서 num 은 양의 정수입니다. 캡처한 문자열에 대한 역참조입니다. 예를 들어, '(.)\1'은 연속적으로 나오는 동일한 문자 두 개를 찾습니다.
\ n 8진수 이스케이프 값이나 역참조를 나타냅니다. \ n 앞에 최소한 n개의 캡처된 부분식이 나왔다면 n 은 역참조입니다. 그렇지 않은 경우 n 이 0에서 7 사이의 8진수이면 n 은 8진수 이스케이프 값입니다.
\ nm 8진수 이스케이프 값이나 역참조를 나타냅니다. \ nm 앞에 최소한 nm개의 캡처된 부분식이 나왔다면 nm 은 역참조입니다. \ nm 앞에 최소한 n개의 캡처가 나왔다면 n 은 역참조이고 뒤에는 리터럴 m이 옵니다. 이 두 경우가 아닐 때 n과 m이 0에서 7 사이의 8진수이면 \ nm 은 8진수 이스케이프 값 nm을 찾습니다.
\ nml n 이 0에서 3 사이의 8진수이고 m 과 l 이 0에서 7 사이의 8진수면 8진수 이스케이프 값 nml 을 찾습니다.
\u n n 은 4 자리의 16진수로 표현된 유니코드 문자입니다. 예를 들어, \u00A9는 저작권 기호(©)를 찾습니다.



전화번호부

전화번호를 찾는 패턴을 살펴볼까요? :

* 응용 패턴 1: [0-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]

 

이렇게 하면 다음 전화번호 같은 거 쉽게 찾습니다.

032-812-6333

헷갈리지 마세요. '-' 문자는 [] 안에서만 특별한 의미가 있습니다.

근데 지역번호가 항상 0으로 시작하더라... 따라서 123-812-6333 같은건 가짜더라...라고 판단이 되면...:

* 응용 패턴 2: 0[0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]

 

로 바꿔주면...

132-812-6333

이런건 가짜라서 안찾습니다. 쉽지요?

눈치 빠른 분들은 웹사이트 등에서 이메일이나 전화번호, 계좌번호 같은 일정한 틀이 있는 입력항목을 어떻게 확인하는지(validation) 아시겠지요? 대부분 regex를 씁니다. 주어진 regex 패턴을 통과하지 못하면 이메일 주소가 올바르지 않습니다 라는 오류 메시지가 나오는거지요:-)

HTML 소스에서 태그 날리기

이것도 쉽습니다. 아무 웹페이지나 가서 HTML 소스 하나 가져오세요. 그걸 울트라 에디터에서 보세요. 거기서 태그만 다 날리고 실제 웹에서 보는 문자들만 남기고 싶다... 어떻게 할까요?

regex 모르면 역시 맨땅에 헤딩입니다. 게다가 손으로 하면 실수하기 쉽구요. 이렇게 하면 끝납니다:

* 응용 패턴1: <.*> => 공백

 

<html>, </html> 식의 태그만 쏙쏙 뽑아 몽땅 날려줍니다. < 로 시작하고 중간에 어떤 문자(.)가 몇번 반복되건(*) 상관없고 끝나는것만 > 로 끝나면 된다...니까요.

근데 문제가 있습니다. . 패턴은 줄바꿈 문자는 찾지 않거든요. 그래서 태그가 길어져 다음 줄로 넘어간건 못찾습니다:

<table border="0" >

 

식의 태그 말입니다. regex는 art라고 했습니다. 마술을 부려 봅시다:

* 응용 패턴2: <[^<>]*>

 

쬐끔 복잡하지요? 근데 역시 < 로 시작해 > 로 끝나는 문자의 연속이라는 패턴은 같습니다. [^<>] 가 헷갈릴 지 모르지만 어떤 문자라도, 심지어 줄바꿈 등 특수문자가 계속되도 상관없지만 단, < 라는 문자와 > 라는 문자만 없으면 된다는 의미거든요.

그러니 줄바꿈이 들어가 잘려진 태그도 찾아내서 날려보내 줍니다. 예술이지요?

진짜 실험해 보세요. 아무리 복잡한 HTML 소스라도 깔끔히 정리해줍니다.

이메일 주소만 찾기

쬐끔 어려운 거 해보고 끝내겠습니다. 더 지겨워들 하기전에 :-)

이메일 추출기 원리입니다. 아니 사실 나쁜데 쓰지 않으면 거의 중급 수준의 regex로 이정도면 여러분 웬만한 문서 작업에서는 남들보다 날라다닐 수 있을 겁니다:

* 응용 패턴 1: [\_\.\-a-z0-9]+@[\_\.\-a-z0-9]+

 

이메일만 잡아냅니다. @ 를 좌우로 해서 _ 나 -, . (이스케이프를 시켜줘야 regex에서 쓰는 특별한 의미가 아닌 문자 그대로의 의미로 받아들임) + 문자나 숫자로 반복되는 문자열을 양쪽으로 잡아내는 겁니다.

지금 해보세요. 이 문서에도 이메일이 몇개 있습니다. 그거 다 잡습니다. 쬐끔 응용을 하면...:

* 응용 패턴 2: ([\_\.\-a-z0-9]+)@([\_\.\-a-z0-9]+)

 

골뱅이 양쪽 문자열을 괄호로 감싸줬지요? 이걸 바꾸기에 응용하는 겁니다. 모든 이메일 주소를 spam 에 악용되지 않게 wankyuchoi@gmail.com 이라는 건 wankyuchoi at gmail.com 식으로 사람이 읽듯 바꿔줘 버리는 겁니다.

겁나게 쉽습니다:

* 바꾸기 패턴: \1 at \2

 

\1 은 첫번째 괄호 결과값 \2 는 두번째 괄호 결과값이라고 했지요? (이런식 표현은 9개까지 가능합니다. 1,2,3,4,5,6,7,8,9... 괄호를 아홉개까지 써서 바꾸기에 응용한다는 거지요. 9개까지 쓸 수 있으면 여러분도 regex 아띠스뜨~ 입니다.)

한번 해보세요. 응용 패턴 2를 찾기 상자에 넣고 바꾸기 패턴을 바꾸기 상자에 넣고 몽땅 바꾸기 말고 하나씩... 그럼 어떻게 바뀌는지 보일겁니다.

예를 들어 게시판에 있는 모든 글속의 이메일 주소를 이런식으로 바꾸라고 윗사람이 생 난리를 친다... 그 사람 regex 라는 거 모른다... 그럼 한 한달 걸리는 걸로 안다... 그럼 한 10초 해서 바꿀 거 생각하고 1달 휴가 갔다 오면 된단 말입니다. 글고 나서, "부장님 저 엄청 뺑이쳤어요... 글이 좀 많아야지요..." 해 보세요.

그럼 "진짜 수고했어..." 그럴겁니다. :-)

그럼 http://www.google.com 이나 ftp://ftp.google.com 식은? :

* 응용 패턴 3: [a-z]+://[^ ]+

 

역시 쉽지요? 알파벳 문자열의 연속(http, ftp) + :// + 공백이아닌([^ ]) 문자열의 연속(+) :

* 응용 패턴4: http(://[^ ]+)

 

http:// 를 ftp:// 로 바꾸고 싶다... 바꾸기에서 ftp\1 이렇게 해주면 되겠지요?

역시 눈치 빠른 분들은 뭔가 번뜩이는게 있을 겁니다. HTML anchor 태그도 쓰지 않았는데 게시판 같은 프로그램을 보면 간단히 URL만 적어도 링크를 자동으로 걸어주지요? 웬만한 게시판들에는 이 기능이 있습니다. 어떻게 하는 걸까요? 예, regex 입니다.

그만 쓸랍니다. 더 나가면 저나 여러분 모두 돌아버릴 것 같아서... :-)

컴퓨터의 노예가 되지 말자

우리 모두 알고 살았으면 합니다. 모르면 수족이 고생이라는 게 맞는 말이더라구요. 정말 주변에 그런 일 많이 봤습니다. 몇년전에 어쩌다 친구 사무실 놀러갔는데 그 놈이 글쎄 한달동안 문서 작업했다고 뭔가 보여주더군요. 몇백 페이지짜리 문서를 공백만 딜리트키 엄청 누르면서 줄맞추고...

눈물이 앞을 가렸습니다. 안타까움에 부둥켜 안고 펑펑 울고 싶었습니다. 한달... 아깝지 않아요? 부모님 묘를 찾지 못해 묘앞의 팻말을 몽땅 뽑아와서 낭패를 본 까망눈 아저씨를 보고 눈물을 흘리던 윤봉길의 마음이었습니다. 윤봉길 의사가 독립운동을 시작한 이유라고 하지요.

그러지 말자구요.

한가지는 명심하자구요. 컴퓨터 만든 놈들은 알게 모르게 귀찮은 거, 단순 반복 겁나게 싫어하는 놈들입니다. 그래서 단순반복 맨땅 헤딩 이런거 안하려고 무지 많은 걸 만들어 놨습니다.

뭔가 이건 인간이 할 짓이 아닌 거 같은데... 로보트 없나? 라는 생각이 드는 무식한 작업이 있으면... 분명 딴 방법이 있습니다.

여러분이 다음에 해당한다면 다시 생각하고 조금만 배워서 활용하고, 그 낭비할 시간에 휴가 가서 재충전하고 옵시다.

  • del 키나 backspace 를 1분 이상 반복적으로 눌러댄 적이 있다.
  • abc 순으로 줄을 정렬하느라 대가리 뽀개진 적이 있다. ( 실제로 이런 사람 많습니다. 정렬이 뭔지 몰라서... )
  • 한 문서에서 하던 일을 또 다른 문서에서 똑같이 하며 "에이, !@$!@#!@$#@$... 이런 단순 무식한 일을 시키고 #$#@$ 이야..." 라고 푸념한 적이 있다.

끝으로.. 걍... 아무도 관심 없으실 수도 있지만... 쩌기 regex에 덧붙여 쬐끔만 프로그래밍, 아니 스크립팅을 배우면 regex로 벌 수 있는 정신적 육체적 시간 X 100 배의 효과를 낼 수 있습니다.

프로그램 아무나 하나...

예, 아무나 합니다. 딴 놈들도 다 그렇게 아무나 하는 줄 알고 시작한 거구, 저도 그랬구요. 앞에 말한 100배 라는 시간... 사실 그런 기초적인 것까지는 1주일이면 배우거든요.

여러분... 컴터의 노예가 되지 말고 부려 먹읍시다.

여러분은 일을 시키는 입장이어야 합니다. 일은 컴터가 해야 하구요. del 키 열라 누르는건... 일은 여러분이 하고 컴퓨터는 노는 겁니다.

명령 하나 때리고 커피 마실때 컴퓨터 혼자 땀 삐질삐질 흘리며 뭔가 일할때... 비로소 여러분은 그 컴퓨터의 master이고 컴퓨터는 slave가 되는 겁니다.

기계의 노예가 되지 맙시다.

우리나라에서 컴퓨터를 소지한 여러분들이 이정도만 아신다고 해도... 우리나라 경쟁력은 하늘을 찌르지 않을까... 또라이같은 생각도 해봅니다.

Regex 에 대해 더 배우고 싶으신 분이 계시면 다음 책 사서 읽어보세요. 단, 번역서 사지 마세요^^ 이유는 짐작하실겁니다.

아마존에 있고 우리나라 컴퓨터 원서 파는 대부분 대형 서점에 있습니다.

더 이상의 regex 책은 없다고 해도 과언이 아닙니다.

 

Mastering Regular Expressions by Jefferey E.F. Friedl - O'Reilly and Associates, Inc.


 

Regex 예제 둘 - 단어장

단어장 만들기 예제를 해보지요. 일단 좀 무식하게 공부하는 사람이 연설문에서 a로 시작하는 단어만 뽑아내겠다고 생각했다고 칩시다:

* 응용 패턴 1: [ \t]a[a-z]+

 

첫번째 '[ \t]a'의 의미는 '공백문자+a'로 시작하는 단어의 뜻이겠지요? 그래야 단어 중간에 있는 a랑 일치하지 않을테니까요. 그다음 [a-z]+ 의 의미는 a로 시작해서 어떤 문자든지(숫자나 공백말고) + 만큼(1번이상) 반복되는 경우를 찾으라는 겁니다.

이렇게 하면 a 는 찾지 않겠지요. +one or more의 뜻이니까요. 근데 짜증나게도 an, as같은 짧은 단어도 찾습니다. 그 정도 단어들은 너무 쉽다.. 최소한 3개는 되야...:

* 응용 패턴 2: [ \t]a[a-z][a-z]+'

 

이렇게 해주면 가운데 [a-z] 가 들어가니 3자리 문자만 나오겠지요. 아참, 대소문자 구분도 해주려면 [a-zA-Z] 가 되어야 한다는 거 잊지 말구요. 아니면 찾고 바꾸기에서 대소문자 체크를 꺼주던가.

근데 문제가 있지요?

"에이... 찾긴 했는데 일일이 뽑아내라구?"

그럴려면 regex 안쓰겠지요. 그럼 a 로 시작하는 단어 단어장 만드는 regex를 순서대로 실험해 보겠습니다. [네오퀘스트 특강 - 클린턴 연설문] 에 다음 regex를 순서대로 적용해 보는 걸로 합니다:

* 응용 패턴 3: [ \t][^a]* => ^p

* 응용 패턴 4: ^[^a]* => ^p

* 응용 패턴 5: [^a-z] => ^p

* 응용 패턴 6: ^[^a]*$ => ^p

* 응용 패턴 7: ^..$ => ^p

* 응용 패턴 8: \r\n\r\n => \r\n

 

사실 유닉스라면 좀더 융통성이 있는데 울트라 에디터는 제한적인 regex만 가능해 단계가 쓸데없이 좀 많습니다.

패턴 3은 a 로 시작하지 않는 단어를 몽땅 찾아냅니다. => ^p 라는 말은 바꾸기 상자에 ^p 를 넣어서 찾은 말을 모두 줄바꿈 표시로 (바꾸기 상자에서 ^p 는 울트라 에디터에서 줄바꿈 기호를 뜻합니다. 그럼 줄줄이 붙여줍니다.) 바꿔주라는 말입니다.

그냥 바꿔주면 다닥다닥 단어들이 붙어버리니까요.

패턴 4는 이렇게 바꾼 문서에 다시 a로 시작하지 않는 단어를 걸러내는 역할을 합니다. 처음 '^'는 줄의 시작을 뜻하며 [^a]* 는 a로 시작하지 않는 문자의 연속(*는 zero or more)을 뜻하니까요.

패턴 5에서 쉼표나 뭐 이런게 줄바꿈으로 바뀝니다. [^a-z]문자가 아닌 건 몽땅이라는 뜻이니까요. 그래서 a-non 식의 문자는 a 줄바꿈 non이 되지요. 그래서 다시 a로 시작하지 않는 문자를 바꿔줘야 합니다.

그게 패턴 6입니다. ^ 줄의 시작 + [^a]* a 로 시작하지 않는 단어의 연속 + $ 니까 줄의 끝이지요? 이걸 다시 ^p 로 바꿔주는 겁니다.

패턴 7은 선택입니다. '.'은 문자 하나를 뜻합니다. 어떤 문자든 상관없습니다. ^.$ 라고 하면 달랑 한줄에 문자 하나 있는 단어를 뜻합니다:

a A

 

이런거지요. 이걸 몽땅 다시 줄바꿈을 하는 겁니다. 두자리 문자도 싫고 세자리도 싫으면 '^..$', '^...$', 자리수만큼 . 을 사이에 넣어주면 됩니다:

aa aaa ab abc an

 

식의 단어가 모두 사라집니다. 마지막으로 줄바꿈이 엄청 많지요? 유닉스에서는 달랑 줄바꿈문자만 있는 줄을 날려버리는 이디엄이 있습니다. ^$ 라는 건데요. 줄의 시작을 알리는문자 바로 뒤에 줄의 끝을 알리는 문자가 오는 줄... 다시 말해 달랑 엔터만 친 줄을 뜻합니다. 이걸 몽땅 공백으로 바꿔주면 다 날라갑니다.

안타깝게도 울트라 에디터는 이걸 지원못합니다. 따라서 \r\n\r\n (윈도우니까) 식으로 다닥다닥 두줄이 연속해 붙어있는 걸 찾은 횟수 0이 나올때까지 서너번 돌려줘야 합니다.

마지막으로 파일 메뉴 np_icon_right_arrow.gif 정렬 메뉴np_icon_right_arrow.gif 파일 정렬메뉴 에서 순서대로 정렬을 해주면... 예 a 로 시작하는 단어장이 완성됩니다.

또 한번 느끼셔야 합니다. 사실 이거 설명만 길지 실제로 해보면 (손에 익으면) 10초도 안걸립니다. 수백 페이지건, 수천 페이지건, 아니 수만 페이지짜리 문서가 수천개가 널려 있건 상관이 없습니다. 울트라 에디터에서는 파일내에서 찾고 바꾸기도 지원을 하기 때문에 특정 디렉토리 아래, 또 그 하위 디렉토리까지 몽땅 파일을 찾아서 패턴을 바꿀 수도 있거든요. 이론상으로는 이 세상의 모든 문서에서 단어를 뽑아내는 일을 컴퓨터에게 시키고 여러분은 휴가를 다녀올 수도 있다는 겁니다 :-)

Regular Expression을 알고 쓰는 여러분은 이미 경쟁력이 남들보다 10년은 앞서갑니다. 아니 여기에 스크립팅 쬐끔 배우면 여러분만의 무슨 영어 문서든 단어장을 10초에 맹그는 도구를 갖게 될 수도 있습니다.

b 로 시작하는 단어장... 쉽겠지요? :

* 응용 패턴 9: [ \t][^ab]* => ^p

 

이렇게 a랑 b 를 함께 찾을 수도 있습니다. a 단어장이랑 b 단어장이 좀 적다 싶으면 앞에 나온 패턴들에 a 부분을 ab로 바꿔주면 되는거지요.

좀 어려웠나요? 그럼 쉬운거 몇개 해보고 비법 전수를 끝내도록 하지요 :-)


Regex 예제 하나


그럼 간단한 예제 하나 해보면서 regex의 맛을 보겠습니다. 도입부에 언급했던 긴~~~ 문서에서 연도를 찾는 걸 해보지요. Regular Expression은 다른 말로 pattern matching이라고도 합니다. 문자속에서 일정한 패턴을 찾는다는 겁니다.

따라서, regex를 쓸때는 어떤 패턴에 적용할지 이 패턴을 머리속에 정의해보는게 중요합니다. 그리고 찾고 바꾸기를 하기전에 반드시 찾기를 먼저 해봐서 내가 생각하는 패턴만을 찾는지 확인해야 합니다. 그래야 엉뚱한 결과를 막을 수 있거든요. 컴퓨터의 모든 부분이 마찬가지지만, regex 역시 면도날과 같아서 좋은 도구가 될 수도 있지만 베일 수도 있다는 뜻입니다.

울트라 에디터 9.0부터는 찾고 바꾸기를 한 것에 대해서도 undo가 가능하니 좀 낫지만 이전 버전에서는 엉뚱하게 바꾸고 나면 정말 하늘이 무너지는 결과를 낳기도 했습니다. 조심 또 조심... 언제나 원본을 백업해 두는 것도 잊지 마세요. 언제나 빠꾸가 가능하도록... :-)

자 그럼 연도의 패턴을 봅시다. 일단 연도는 네자리수만 있지요. 물론 그 이전도 있지만 편의상 여기선 4자리만 따지겠습니다:

* 찾고자 하는 패턴: 1234, 1940, 1999, 2000, 2002 등등

* 정규 표현식: [0-9][0-9][0-9][0-9]

 

원래 유닉스라면 [0-9]{4} 식으로 4자리 반복가능을 직접 지정해 줄 수 있는데 울트라에디터에선 안됩니다. 저렇게 무식하게 해줘야 하지요.

일단 4문자를 찾는다는 건 '[]' 가 네번 반복되기 때문에 알 수 있습니다. '[]' 안에 0-9 가 있으니, 0,1,2,3,4,5,6,7,8,9 만 찾는다는 것도 알 수 있습니다. [0-9] 는 '[0123456789]' 와 의미가 같으니까요. 따라서 당연히 1234, 1940, 1999, 2000, 2002 식의 내자리 숫자만 찾습니다. 좀더 응용해서 "2천년대라는 건 안다..."싶으면 '2[0-9][0-9][0-9]'라고 해주면 되겠지요? 2천 10년대만 찾는다면? 201[0-9] 예술입니다 :-)

지금 실험해보세요. 울트라 에디터에서 CTRL+F 를 눌러 검색 창을 연 다음 (정규식 에 체크하고) 찾기 텍스트 상자에 2[0-9][0-9][0-9] 라고 입력하고 찾아보세요. 그럼 이 문서에 나온 2천년대 숫자만 찾을 겁니다.

Regex 는 Art 다

regex는 art다... 상상력을 동원해보세요. 이것만 가지고도 여러분은 별짓 다할 수 있습니다:

* 응용 패턴 1: 1[0-9][0-9]1 (1231, 1991 식의 연도만 찾기)

* 응용 패턴 2: 19[0-9][0-9] (1900 년대만 찾기)

 

참고로 [0-9]\d 로 대체가 가능하다고 했습니다. 2[0-9][0-9][0-9]2\d\d\d 와 같다는 말입니다.

그럼 바꾸기 해보까요? 지금은 2천년대라 의미가 없지만 20세기에는 80년대, 60년대 식의 표현을 썼습니다. 60은 1960, 80은 1980인거지요.

20세기에 만들어진 1만페이지짜리 문서에 모두 이렇게 돼 있는 겁니다. 이것도 Y2K 버그지요. 90년대 정치상황으로는.... 90년대와서는...

이 문서의 이런 시의성 떨어지는 표현들을 모두 1980년대, 1990년대로 바꾸고 싶으면 어떻게 찾으면 될까요? 쉽지요? :

* 시험 패턴 1: [0-9][0-9]년대

 

해보세요. 아주 잘찾습니다. 근데 너무 잘 찾아서... '1990년대'까지 찾습니다. 아무 생각없이 찾고 바꾸기 해버리면 191990년대 식의 표현도 등장할 수 있다는 겁니다. 그래서 찾기를 먼저해서 실험해 보라는 겁니다. :

* 시험 패턴 2: 공백[0-9][0-9]년대

 

공백은 공백을 표시하기 위한 겁니다. 찾기 창에 공백이라고 쓰라는 게 아니고...

앞에 공백을 하나 넣고 찾아보세요. 이제 1990년대 문제는 사라졌지요? 근데 공백이 아닌 \t 문자가 앞에 있는 '\t90년대'식의 표현은 찾지 못할 겁니다:

* 시험 패턴 3: [ \t][0-9][0-9]년대

 

이럼 다 찾습니다. [ \t] 의 의미는 공백 또는 탭 문자니까요. 이걸 더 쉽게 하려면 '\W[0-9][0-9]년대'라고 해주면 됩니다. 근대 안타깝게도 \W는 한글도 포함됩니다. 한글이 원래 컴퓨터에게는 이상한 특수코드로 입력이 되기 때문에... 그래서 조심하란 겁니다:

* 최종 찾기 패턴: [ \t]([0-9][0-9])년대

 

안전하게 한글을 다치지 않기 위해. 여기 나오는 괄호부분이 나중에 바꾸기에 활용을 하겠다는 뜻입니다.:

* 바꾸기 패턴: 19\1년대

 

\1 은 앞에 괄호로 감싼 부분 [0-9][0-9] 에서 찾은 결과값으로 대체하라는 뜻입니다. 90년대 를 찾았다면 90이 그 값이기때문에 1990년대 로 변환이 되지요.

한번 해보세요. 아니 해봐야 뭔소린지 압니다. 꼭 해보세요. regex의 빠워~를 한번 느껴보세요. 몽땅 바꾸기 하지 말고 하나씩 해보면서 어떻게 찾고 변하는지 보면... regex의 마술을 느낄 수 있을 겁니다. 아니 무한한 가능성... 아... 앞으로 수없는 뺑이를 줄일 수 있겠다는 안도감... 뭐 그런게 느껴지신다면 이 길고 긴 난수표같은 제 글도 더 이상 멍멍 np_icon_dog.gif 만은 아니겠지요 :-)

두번째 예제로 이어집니다.



정규 표현식의 기본

앞에서 잠깐 언급했지만 한대가리 한다는 사람들도 regex(앞으로 정규표현식 다 regex 라고 하겠습니다)를 접하면 "오 신이시여..." 를 연발하게 됩니다. 도대체가 예술이라고밖에 생각할 수 없는, 인간의 머리로 만들어냈다고 믿을 수 없는 경지의 기술이거든요. 그래서 그만큼 어렵기도 합니다.


regex를 다 안다
라고 하는 놈은 이 세상에 없습니다. 컴퓨터 전문 출판사 O'Reilly 에서 Mastering Regular Expressions를 출간한 저자라고 해도 regex 전문가라는 말은 해줄 수 있어도 regex의 모든 것을 다 아는 사람이라고 할 수는 없을 정돕니다. regex를 아는 대부분 컴쟁이들이 어울리지 않게 regex 쫌 할 줄 안다라고 겸손아닌 겸손을 떠는 것도 이때문입니다. 그도 그럴 것이 regex는 끝이 없습니다. 그래서 끝까지 배울 수가 없지요.

regex 실력은 이 도구를 활용하는 여러분 상상력에 달려 있거든요. 무슨소린지 배워보면 압니다. regex가 가장 빛을 발휘하는 환경은 Unix 프로그래밍이나 문서 작업에서입니다. regex 자체가 Unix 전문가들이 타자치는게 귀찮아서 조금씩 쌓기 시작한 내공이거든요. MS의 윈도우 환경에서도 조금씩 흉내를 내고 있기는 합니다만 Unix 의 기본 도구라는 점에서 ( 당연히 Linux에도 기본 도구입니다 ) 윈도우 사용자보다는 Unix 또는 Linux 사용자에게 친숙한 도구이기도 합니다.

하지만 윈도우 사용자 여러분도 regex의 맛을 느끼고 기본적인 부분을 일상생활속에서 활용할 수 있습니다. 대부분의 고급 편집기 (MS 워드 포함)에 이미 정규식이 정식 도구로 자리잡고 있거든요. 가장 기본에 충실하게 regex를 구현하고 있는 편집기로는 Ultra Editor가 있습니다. MS 워드에도 있긴 합니다만, MS가 언제나 그렇듯이 표준을 무시하고 제멋대로 만들어 놓아 거의 쓸모가 없습니다.  울트라 에디터는 쉐어웨어로 누구나 다운로드 받아 사용할 수 있습니다. [Ultra Editor 다운로드] 울트라 에디터에서 사용하는 regex 구문은 Unix 와 조금 다릅니다. Unix 호환이 되도록 설정을 바꿀 수 있지만, 이렇게 해도 조금 다르니 그냥 좀 다르다는 사실만 염두에 두세요.

일단 울트라 에디터에서 유닉스 정규식 호환이 되도록 설정을 바꿔줍니다. 고급 메뉴 설정 메뉴  찾기 탭 Unix 정규식 사용을 체크 이렇게 해줘야 Unix 랑 비슷해지고 또 그래야 여기 나온 예제 해볼 수 있습니다. 그대로 따라해도 안된다 싶으면, 이거 체크했는지 확인해 보세요. 또, 프로그래밍이 그렇듯이 정규식 또한 한치의 오차도 허용하지 않습니다. 단 한개의 오타라도 말도 안되는 결과가 나올 수 있습니다. 울트라 에디터에서 사용할 수 있는 regex는 전체 regex의 1/1000 정도밖에 되지 않습니다. 하지만, 이것만 가지고도 여러분은 regex로 마술을 부릴 수 있습니다

정규식 표현(Unix 구문)

  • \ 특수 기호의 시작을 나타냅니다. 영문으로는 원래 슬래쉬인데 한글 문서에서는 원화 표시로 나오는 그 문자 말입니다. 흔히 말하는 이스케이프 문자(escape character)를 뜻합니다. 예를 들어, n은 n이라는 문자를 말하지만 \n 이란건 개행문자(newline)를 뜻합니다. 여러분이 키보드로 쳐넣을 수 없는 특수문자를 말하는 겁니다. 왜 escape문자라고 하느냐... 원래의 의미에서 탈출해 다른 의미를 갖게 해주기 때문입니다. '\' 이 문자 자체의 문자적 의미를 되살리려면 두번 붙여 써 주면 됩니다. \\ 이렇게...
  • ^ 줄의 시작 문자와 일치합니다. 줄의 시작문자라는 것 역시 눈에 보이거나 키보드로 쳐넣을 수 있는게 아닙니다.
  • $ 줄의 끝 문자와 일치합니다. 역시 마찬가지구요.
  • * 앞에 어떤 문자가 있건 그 문자가 0번 혹은 그 이상 반복되는 걸 말합니다.

예를 들어, a* 라면 a가 안나오는 경우 (a 0번 반복), a (1번), aa (2번)...

  • + 앞에 어떤 문자가 있건 그 문자와 하나 또는 여러 개와 일치합니다. 적어도 하나의 문자가 발견됩니다.

예를 들어, a+ 라면 a가 안나오는 경우는 포함되지 않고 최소한 a가 1번은 있어야 합니다.

  • . 개행 문자 이외의 어떤 문자라도 일치합니다. a. 라고 하면 a로 시작해서 어떤 문자든 문자 하나가 따라온다는 뜻입니다. ab, ac, ad 식의 ... a 는 a뒤에 아무것도 없기 때문에 일치하지 않습니다.
  • [] 괄호 안에 열거한 문자 중 어떤 것이라도...의 뜻입니다. '[abc]D' 라고 하면 aD, bD, cD 식의 문자가 일치합니다. 순서가 있는 문자 조합인 경우, 예를 들어, [a-z] 라고 하면 알파벳 전부, [0-9] 라고 하면 숫자 전부를 말합니다. 대소문자 일치 기능을 쓰지 않으면 '[a-zA-Z]'라고 해야 알파벳 대소문자 전부를 뜻합니다. 부정할 수도 있습니다. [^a-z] 라고 하면 알파벳을 제외한 모든 문자가 됩니다.

이스케이프 문자 얘기했지요? [a-zA-Z0-9] 라고 하면 특수문자를 제외한 모든 문자라는 뜻이 됩니다. 매번 이렇게 쓰는 거 귀찮아서 유닉스 컴쟁이들은 이스케이프 문자를 활용해 이런 것도 만들었습니다.

  • \d 숫자와 일치. [0-9] 과 같습니다. 여기서 d는 digit을 말합니다.
  • \D 숫자가 아닌 문자. [^0-9] 와 같습니다.
  • \w 단어 문자와 일치. [a-zA-Z0-9]
  • \W 단어가 아닌 문자와 일치. 여기서 w는 whitespace를 말하는데요. whitespace란 공백문자를 뜻합니다. 단순히 스페이스바로 넣는 공백문자뿐 아니라 탭이나 기타 formatting의 의미를 갖는 공백, 탭, 개행문자 등등을 모두 포함합니다.
  • \n LF 문자. linefeed를 뜻합니다. 개행문자로 Unix 에서는 이것만 씁니다.
  • \r CR 문자. carriage return을 뜻합니다. 윈도우에서는 \r\n 두개를 개행문자로 씁니다.

\r 과 \n 은 실제 타자기에서 나온 겁니다. \n은 타자기에서 다음줄로 넘어가는 걸, \r은 손으로 밀어서 타자 위치를 잡는 걸 말하거든요. 컴퓨터 편집기는 이걸 알아서 하거나 여러분도 return이란 걸 치지요? Enter 키가 옛날엔 Return 키였습니다. 지금도 return이란 말 쓰기도 하구요.

  • \s 공백, 탭. 개행문자, 즉 새로운 줄과는 일치하지 않습니다. \w보다 작은 뜻.
  • \S 공백이 아닌 문자와 일치. 역시 개행 문자, 즉 새로운 줄과는 일치하지 않습니다. \W보다 작은 뜻.
  • \t 탭 문자. 탭문자 역시 눈에 보이지 않지요. 긴 문서에서 탭문자 없애느라고 손으로 하루 종일 뺑이치는 분을 본 적도 있습니다. \t 를 공백으로 찾고 바꾸기 하면 1초면 끝날 일을...

참고로 특수문자 얘기가 나왔는데, 한글은 정확하게 잡지 못할 수도 있습니다. 영문의 입장에서 보면 (regex도 알파벳을 기준을 하기 때문) 한글은 모두 특수문자에 들어갑니다. escape 문자를 사용할땐 주의하세요.

  • () 오 신이시여.... 가 드디어 나옵니다. regex의 진수는 찾기에 있지 않습니다. 바로 바꾸기에 있습니다. 앞에서 찾은 내용을 가공해서 바꾸기에 활용한다는 뜻입니다. regex가 아니라면 절대 할 수 없는 일입니다.

괄호 '()' 를 쓰는 예를 들어볼게요. Wankyu Choi <wankyuchoi@gmail.net> 식의 이메일 표기를 1000페이지 문서에서 찾아서 HTML 메일 태그로 변환하고 싶다... 이거 손으로 하려면 얼마나 오랜 시간이 걸릴까요? 잠시 고민...

다시 말해 다음과 같은 메일 태그로 바꿔주는 겁니다:

<a href="mailto:wankyuchoi@gmail.com">Wankyu Choi</a>

이런건... regex 아니면 진짜 맨땅에 헤딩을 넘어 새끼 손가락으로 물구나무 서기나 다름 없습니다. regex를 쓰면:

* 찾기 표현: (이메일 주소앞에 나오는 이름을 찾는 regex)@(이메일 주소를 찾는 regex)

* 바꾸기 표현: <a href="mailto:\2">\1</a>

라고 해주면 된다는 겁니다. ' \ ' 문자를 사용했으니 \11 과 다르다는 건 알겠지요? 바로 앞에 나온 첫번째 괄호 부분 찾기의 결과값을 뜻합니다. \2 는 두번째 괄호... 1000페이지.... 이걸 손으로 하겠다는 사람이 있다면 1주일은 넘게 걸릴 겁니다.

앞에서도 말했지만 regex의 한계는 여러분 상상력의 한계와 일치합니다. regex를 art라고 하는 이유가 여기 있어요. 쓰는 사람의 손에서 예술적 가치가 나옵니다 :-)

3부로 넘어갑니다.

이번 글에서 제가 하는 말은 그냥 무시해버리면 아무짝에 쓸모없는 멍멍  이 될지도 모르지만 잘만 쓰면 여러분이 매일 매일 생노가다하며 보내는 시간을 꽤나 줄여줄 수 있는 보물이 될 수도 있습니다.

왕도는 없어도 비법은 있다

사람들은 어떤 식으로건 일을 하고 삽니다. 돈벌이를 못하는 백수라도 손가락 까딱 안하고 누워있지만은 않으니까요.


사람들에게 똑같은 일을 시켜보면 출발점과 결과물은 같더라도 어떤 사람은 10분안에 끝날 일도 어떤 사람은 일주일이 걸리기도 합니다. 우린 이걸 능력, 또는 실력이라고 하지요.
 

누군 10분에 하는 일을 1주일 걸려 하는 사람은 다른 일거리를 줘도 역시 대부분 1주일에 하게 됩니다. 이보다 빨리 일처리를 하는 사람에겐 언제나 경쟁에서 뒤쳐지게 되지요. 
 

사람은 귀찮은걸 가장 싫어하는 동물입니다. 그래서 도구를 만들어냈지요.
 

언어만 해도 그렇습니다. 글쓰는게 귀찮아서 타자기가 만들어졌고, 급기야 두들기기만 하면 되는 컴퓨터 워드프로세서가 만들어지기도 했습니다.
인간은 도구를 사용할 줄 아는 동물입니다. 도구를 얼마나 잘 사용할 것인가... 역시 인간이 언제나 고민하는 일이기도 합니다.
살면서 뭔가 인간이라면 하지 않을 짓을 단순무식과격하게 반복하고 있다는 생각이 들때... 과거 누군가 똑같은 고민을 했을 지도 모른다는 뜻입니다.
 

그래서 또 도구를 만들고, 그 도구를 갈고 닦고, 그 도구를 100% 활용할 수 있는 비법을 만들어놓았을 수도 있다는 거지요.
이번 글에서 제가 하는 말은 그냥 무시해버리면 아무짝에 쓸모없는 멍멍  이 될지도 모르지만 잘만 쓰면 여러분이 매일 매일 생노가다하며 보내는 시간을 꽤나 줄여줄 수 있는 보물이 될 수도 있습니다.
 

수많은 골수 컴쟁이들이 (컴쟁이치고 귀찮은거 좋아하는 사람없고, 단순 노가다 좋아하는 사람이 없습니다) 만들어놓은 정규식(regular expression; 줄여서 regex)이라는 비법입니다.


컴퓨터로 노가다를 한다?

컴퓨터가 가장 잘하는 짓이 두가지가 있는데요. 하나가 숫자 놀음이고 또 다른 하나가 문자 놀음입니다. 사실 컴퓨터 내부적으로는 모두 숫자 놀음이기 때문에 문자 놀음 역시 숫자 놀음이긴 하지만...
 

숫자 놀음이야 계산기 두드리는 수준 이상의 고급 작업을 하는 경우라면 어차피 분야별로 공부를 하셔야 할테니 (예: Excel 같은 spreadsheet ) 누구나 늘상 접하는 문자 놀음에 대해서만 생각해 봅시다.
 

사실 여러분이 컴퓨터로 하는 일의 절반 이상이 문자 가지고 하는 일입니다. 그런데 편집기(editor)를 쓰건 워드(word processor)를 쓰건 알게 모르게 맨땅에 헤딩하는 경우가 수없이 많습니다. 그 맨땅에 헤딩하는 정도와 회수를 줄이자는게 이번 주제입니다.

컴퓨터 노가다의 전형적인 예

무슨 소린지 감이 잘 안오시나요? 간단하게 실험을 해보겠습니다. 여러분이 어떤 출판사에서 일을 한다고 가정해 보세요. 네오 출판사에서는 금번에 미국 대통령 연설만 몽땅 모아서 거기 나오는 어휘들로 바퀴벌레 책을 내기로 했습니다. 직원들에게 다음처럼 이런 일을 시킵니다.


  • 미국 대통령 연설문은 모조리 모아라.
  • 연설문에 나오는 모든 단어를 추출해 a,b,c 순으로 정렬해라.
  • 이렇게 만든 단어 목록을 가지고 3자리 이상되는 단어만 다시 뽑아 단어집을 만들어라. ( 세자리 미만은 큰 의미가 없으니까 )

예를 들어, 미국 대통령 연설문이 딱 100개 된다고 해보지요. 더 되겠지만 그정도만 된다고 칩시다. 네오퀘스트 특강(실전 독해)에 사용된 클린턴 연설문에 나오는 단어가 중복을 허용한 상태로 8,000개쯤 됩니다.
 

이게 100개면 800,000개란 얘기지요? 800,000 단어를 손으로 추려서 중복된거 골라내고... 이걸 다시 3자리 이상만 골라내고...
내가 그런 과격한 노가다를 할 가능성이 있을까?
 

이렇게 생각하실 지 모르지만 그냥 한가지 예일 뿐입니다. 규모는 작아도 하루에도 몇번씩 우리는 이런 일을 하고 삽니다.
 

짧은 문서의 찾고 바꾸기(search and replace)일 수도 있고, 여기 나온 예처럼 몇달이 걸리는 수작업일 수도 있습니다. HTML 문서 소스를 뽑아 태그만 몽땅 없애고 본문만 남기는 작업일 수도 있고, 스팸 회사에서 게시판 뒤져가며 이메일만 추출해내는 일일 수도 있습니다.
 

그래도 무슨 소린지 와닿지 않는다면 생활속에서 흔히 볼 수 있는 예를 하나 들어볼까요?
 

여러분이 웬 긴~~~ 문서를 읽다가 몇년도인가 재밌는 일이 일어났다고 하는 걸 봤다고 칩시다. 한 일주일쯤 지나고 나서 친구에게 그 얘기를 하다 다시 해당 부분을 찾아보려니 그 긴~~~ 문서에서 어느 부분이었는지 또는 몇년도였는지조차 기억이 안나는 겁니다. 이 문서가 또 100페이지쯤 되고 대충 어느 위치였는지도 모른다면 해당 부분을 찾는데 시간이 얼마나 걸릴까요?
 

실제로 이런 일을 종이책 뒤지듯 하루 종일 뒤지는 분들도 있습니다. 그나마 편집기나 워드의 찾기(find) 기능을 활용하는 분이라면 형편이 좀 나은 거지요.
 

앞에 나온 단어찾기 노다가... 여러분이 회사에서 살아남으려면 죽어도 해야 하는 일이라면, 며칠, 아니 몇달 걸리면 끝날까요?


아마 이글을 읽는 분의 컴퓨터 숙련도에 따라 답이 달라질 겁니다. 10초를 외치신분 혹시 있나요? ;-)
 

이런게 바로 사람이 해야할 일이 아니라 컴퓨터가 해야할 일입니다. 컴퓨터란 놈이 제일 잘하는 짓이니까요. 그럼 컴퓨터 숙련도에 따른 시간을 재 볼까요?


  • 프로그래밍이 가능한 엔지니어 - 사용하는 컴퓨터의 처리속도에 따라 다르지만 대충 10초 내에 저 일 끝날 수 있습니다. 또, 프로그램으로 만들어 놓을 것이기 때문에 다른 어떤 문서를 가져다 줘도 저 기능은 10초내에 모든 문서를 해결할 수 있습니다. 스스로 도구를 창조하고, 동일한 종류의 일에 대해서는 언제나 이 도구를 사용합니다.
  • 정규식 표현을 알고 있는 파워 유저 - 앞에서 이름만 소개한 regular expression 이라는 겁니다. 사실 배우기가 무지 까다로워서, 아니 제대로 가르쳐주는 사람이 없어서, 그만큼 알기도 어려워서, 이 빈곤의 악순환으로 극소수 컴쟁이들만이 향유하는 그야말로 인간이 만들어낸 최고의 논리 장난감입니다. 이걸 아는 사람이면 10초는 아니더라도 저 일 1시간내에 끝납니다.
  • 프로그램도 정규식도 모르는 그냥 유저 - 역시 숙련도에 따라 다르지만 길게는 며칠, 심하게는 1년이 걸릴 지도 모르지요. 또 정확성을 기대할 수도 없습니다. 손으로 하는거라 빼먹을 수도 있고, 짜증나서 안할 수도(눈가리고 아웅) 있으니...

한때 Python이란 언어를 만든 그 이름도 야시꾸리한 Guido Van Rossum 이란 천재 컴쟁이가 Computer Programming for Everyone이란 프로젝트를 진행한적이 있습니다. 이거 조금만 하면 저 사람들 뺑이치는걸 백분의 1, 아니 백만분의 1로 줄일 수 있을텐데... 하는 안타까움에 시작한 자선 사업이었데요. 근데 자금 부족, 중생들의 참여가 형편없어 실패했다는군요. 선각자의 뜻이 그대로 실현이 될 수는 없는 노릇이듯이... 
 

여기서 제가 하고자 하는 얘기는 거기까지 가지도 않습니다. 물론 프로그래밍의 아주 기본적인 것만 배워도 여러분 삶이 얼마나 윤택해질 지 마구 설파하고 싶지만 모든 이에게 프로그래밍이 좋고 즐거운일은 될 수 없을테니까요.

하지만, 정규 표현식(regular expression) 정도라면 충분히 가능성이 있다고 생각합니다. 그 정도만 되도 여러분 삶이 윤택을 넘어 젖과 꿀이 흐르는 가나안이 될 수도 있다고 믿거든요. 10초면 끝날 수 있는 일을 1주일 걸려 해야만 한다면, 비극이 아닐까요? 


차리리 하루 정도 걸린다고 구라치고 잽싸게 끝낸다음 나머지 시간에 땡땡이를 칠 수 있는 여유를 갖는 것... 그게 진정한 호모 사피엔스, 인간의 모습이 아닐까요?
 

비법 전수 2부에 이어집니다.



http://www.gethifi.com/tools/regex

위 사이트에서 정규식 연습 할수 있음!!

+ Recent posts