![]() Guido Socher 글쓴이 소개: 그는 리눅스가 자유 시스템이고, 전세계의 리눅스 사용자와 함께 일을 할 수 있다는 것이 매우 즐거운 일이기 때문에 리눅스를 사랑한다. 남는 시간은 여자 친구와 함께 보내거나, BBC의 세계 라디오 서비스를 듣거나, 자전거로 교외를 돌아다니거나 리눅스를 가지고 논다. 목차: 들어가는 글 간단한 예제 문법 규칙 Text를 편집하면서 정규 표현식 쓰기 |
정규 표현식![]() 들어 가기 전에: 정규 표현식은 문맥 민감(context sensitive)한 탐색을 하거나, 텍스트를 수정할 때 매우 많이 쓰인다. 정규 표현식은 좋은 에디터나, 파서 프로그램과 언어세 쓰인다. 들어 가는 글정규 표현식이 emacs나 vi같은 에디터나 grep/egrep 과 awk, perl , sed 같은 프로그램에서 사용되는 것을 볼 수 있다.. 정규 표현식은 향상된 문맥민감(context sensitive) 탐색이나 텍스트의 수정 등에서 사용된다. 정규 표현식은 텍스트의 문자열과 매치되는, 형식이 아주 잘 갖춰진 표현이다. 필자는 몇 년 전에 정규 표현식을 쓰는 사람을 보고서 정규 표현식에 빠져 들게 되었다. Text를 입력하는 일과 수정하는 일은 보통 많은 시간이 걸리게 되게 마련인데, 그 사람은 몇 초만에 일을 하고 말았다. 화면에 있는 표현식을 봤을 때 이해할 수는 없었다. 표현식은 점(.)과 슬래쉬(/)로 시작하고 몇몇 문자의 조합으로 이루어져 있었다. 그때부터 정규 표현식이 어떻게 돌아가는지 배우기 시작했고, 곧 정규 표현식이 어렵지 않다는 것을 알았다. 단순히 간단한 문법 규칙들로 이루어 졌다는 것을 알았다. 정규 표현식은 유닉스 환경에서 매우 많이 사용되고 있지만, '표준 정규 표현 언어'는 있지 않다. 약간 변형된 표현식이 많다. 예를 들어 'grep' 프로그램에도 'grep'과 'egrap'의 두가지가 있다. 위의 두 프로그램은 약간 다른 정규 표현식을 사용한다. perl은 가장 많은 정규 표현식을 가지고 있다. 다행이도 모든 표현식은 같은 원리를 가지고 있다. 한번만 이런 기본적인 원리를 이해하고 나면, 서로 다른 표현식이라도 쉽게 배울 수 있을 것이다. 이 기사에서는 기본적인 것들만 소개할 것이고, 나머지는 독자들이 직접 매뉴얼 페이지를 보면서 서로 다른 프로그램에 대해서 배울 수 있다. 간단한 예제다음과 같은 회사의 전화번호 리스트가 있다고 가정을 해보자. Phone Name ID ... ... 3412 Bob 123 3834 Jonny 333 1248 Kate 634 1423 Tony 567 2567 Peter 435 3567 Alice 535 1548 Kerry 534 ... 500명의 직원을 가진 회사이다. 위의 파일은 단순히 아스키 코드로 되어있는 Text 파일이다. 전화번호가 1로 시작되는 사람은 1번 건물에서 근무하는 사람들이다. 어떤 사람들이 1번 건물에서 근무하는지 알고 싶으면 어떻게 하여야 할까? 다음과 같은 정규 표현식이 그 답을 말해 준다 : grep '^1' phonelist.txt or egrep '^1' phonelist.txt or perl -ne 'print if (/^1/)' phonelist.txt 위의 표현식은 첫 글자가 1인 모든 줄을 찾으라는 표현식이다. "^"는 줄의 시작을 나타낸다. 즉 첫 글자가 1인 줄을 찾으라는 것이다. The syntax rules한 문자 패턴기본적인 정규 표현식은 한 문자 패턴(single-character pattern)으로 이루어져 있다. 정확히 한 문자와 맞는 경우를 말한다. 한 문자 패턴의 예는 위에 있는 1을 찾는 문제이다. 한 문자와 필적을 한다. 다른 한 문자 패턴의 예는 다음과 같다: egrep 'Kerry' phonelist.txt 위의 패턴은 단순히 한 문자 패턴으로 구성되어 있다.( K,e.. 와 같은 문자) 문자들은 함께 그룹화 되어 집합이 될 수 있다. 집합은 '['로 시작해서 ']'로 끝나고, 그 사이에 문자들이 있는 것을 말한다. 집합 또한 한 문자 패턴이다. 집합 안에 있는 한 문자 만이 텍스트의 탐색에서 짝을 이룰 수 있다. 예를 들어 : [abc] a, b 혹은 c가 매치되는지 보는 한 문자 패턴이다. [ab0-9] a, b 혹은 숫자에 해당되는지를 나타내는 한 문자 패턴이다. [a-zA-Z0-9\-] 대문자, 소문자, 숫자, 음수 기호(-)의 매치를 나타내는 한 문자 패턴이다. 다음과 같이 해보자: egrep '^1[348]' phonelist.txt 위의 표현식은 13, 14, 18로 시작되는 줄을 탐색하게 된다. 대부분의 아스키 문자들은 매치되는지를 알아 보기 위해서 쓰지만 특별한 의미를 나타내는 아스키 문자들도 있다. 예를 들어 '['는 집합의 시작을 나타낸다. '-'는 범위를 나타내는 특별한 의미를 가진 문자이다. 이러한 문자를 의미없이 쓰려면 문자 앞에 '\'를 붙이면 된다. [a-zA-Z0-9\-]가 이러한 예이다. 일부 변형된 정규 표현식에서는 약간 다른 표현이 쓰인다. 특별한 문자가 '\'와 함께 시작되는 정규 표현식을 쓰는 경우도 있다. 이 경우 일반적인 아스키 문자를 나타내기 위하여 '\'를 제거해야 한다. 점(.)은 매우 특별한 의미를 가진 특수 문자이다. newline 문자를 제외한 모든 문자를 나태낸다. 예를 들어 : grep '^.2' phonelist.txt or egrep '^.2' phonelist.txt 위의 표현은 두 번째 문자가 2이고, 첫 문자는 아무 문자나 나오는 줄을 탐색하는 표현이다. 집합의 처음을 '['로 사용하지 않고 '[^'로 사용하면 역집합을 나타낸다. '['뒤에 있는 '^'는 줄을 시작을 나타내지 않고 역을 나태낸다. [0-9] 0과 9사이의 숫자를 나타내는 한 문자 패턴이다. [^0-9] 숫자가 아닌 문자를 나타낸다. [^abc] a,b,c 문자가 아닌 모든 문자를 나타낸다. . 점(.)은 newline 문자가 아닌 모든 문자를 나타낸다. 즉 [^\n]와 같다. 1로 시작되지 않는 줄을 찾기 위해서 다음과 같이 쓸 수 있다 : grep '^[^1]' phonelist.txt or egrep '^[^1]' phonelist.txt Anchors'^'가 줄의 시작을 나타낸다고 위해서 배웠다. Anchor는 정규 표현식에서 문자의 위치를 나타내거나 텍스트에 없는 문자를 나타내는 특별한 문자이다. ^ 줄의 시작을 나타낸다. $ 줄의 끝을 나타낸다. phonelist.txt에서 회사 ID 번호가 567인 것을 찾기 위해서 다음과 같이 쓸 수 있다 : egrep '567$' phonelist.txt 위는 줄의 끝이 567인 줄을 찾는다. Multipliers Multiplier는 한 문자 패턴이 텍스트에서
얼마나 자주 반복되는지를 결정한다.
Note: 다양한 VI 프로그램들은 위와 같은 작업을 하기 위해서 매직 옵션을 두고 있다. 전화 번호 리스트에서의 예: .... 1248 Kate 634 .... 1548 Kerry 534 .... 1로 시작하고, 뒤에 숫자가 몇 개 오며, 적어도 하나의 공백이 있고, K로 시작하는 이름을 찾기 위해서는 다음과 같이 하면 된다. grep '^1[0-9]\{1,\} \{1,\}K' phonelist.txt 또는 *와 [0-9]와 공백을 반복하여 : grep '^1[0-9][0-9]* *K' phonelist.txt 또는 egrep '^1[0-9]+ +K' phonelist.txt 또는 perl -ne 'print if (/^1[0-9]+ +K/)' phonelist.txt '*'는 바로 앞 한 문자의 반복을 나타낸다. 즉 "23*4"는 "2와3, 아무 문자나 여러 개, 4"를 나타내지 않는다.(이의 경우 "23.*4"이다.) 위의 식은 "한 개의2, 여러 개의 3, 한 개의4" 를 나타낸다. 또한 '*'는 게걸스럽다는(greedy) 걸 알아야 한다. 즉, '*'는 최대한 오른쪽으로 확장을 한다. The expression ^1.*4 위의 식은 다음 줄 전체와 매치가 된다. 1548 Kerry 534 시작부터 4로 끝나는 걸 의미 하지, 154와 매치되지는 않는다. grep에서는 별로 중요하지 않지만, 에디트를 하거나 대치를 할 경우 매우 중요하다. Parentheses as Memory(메모리로서의 괄호?)'Parentheses as Memory' 구조는 정규 표현식의 매치 방법은 변하지 않지만, 괄호로 묶인 부분은 기억되었다가 뒤에 쓰일 수 있다. 기억된 부분은 변수를 이용해서 뒤에 이용될 수 있다.
첫 번째 'Parentheses as Memory' 구조는 1 번 변수를 통해서
이용되고, 두 번째 'Parentheses as Memory' 구조는 2번 변수를
써서 이용된다.
예: 식 [a-z][a-z] 은 두 개의 소문자와 매치된다. 이제 우리는 'otto'와 같은 텍스트를 찾기 위하여 변수를 쓸 수 있다. egrep '([a-z])([a-z])\2\1' 변수 \1은 'o'를 저장하고 있고, \2는 't'를 저장하고 있다. 'anna'와 같은 이름과는 매치되지만, 'yxyx'와 같은 이름과는 매치되지 않는다. 'Parentheses as Memory' 구조는 'otto'나 'anna'와 같은 이름을 찾는데는 많이 쓰이지 않고, 대신 에디트를 하거나, 대치(substitution)을 하는데 많이 쓰인다. Text를 편집하면서 정규 표현식 쓰기에디트를 하기 위해서 vi나 emacs혹은 perl과 같은 것을 사용해야 한다. emacs에서는 M-x query-replace-regexp 혹은 query-replace-regexp 버튼을 사용사용한다. 또는 replace-regexp 명령을 사용할 수 있다. query-replace-regexp만 interactive하고, 나머지는 그렇지 않다. vi에서는 대치 명령이 %s/ / /gc이다. 여기서 '%'는 '파일 전체'를 나타낸다. 아니면 적당한 범위를 지정해 줄 수도 있다. vim에서는 shift-v를 친 후에 대치하기를 원하는 부분을 지정해 줄 수 있다. 여기서는 vim에 대한 자세한 설명을 해 줄 수 없다. 메뉴얼을 스스로 보라. 'gc'는 interactive한 것을 나타낸다. s/ / /g는 interactive하지 않음을 나타낸다. interactive하다는 것은 매치되는 것을 만날 때마다, 대치를 할 것인지 안 할 것인지 물어 보는 것을 말한다. perl에서는 다음과 같이 사용할 수 있다. perl -pe 's/ / /g' 몇 가지 예를 보자. 위에 나온 회사에서 전화 번호가 바뀌어 1과 2로 시작하는 번호는 두번째 위치에 2가 추가 되었다. 예를 들어 1423은 14223으로 되었다. 예전의 리스트 : Phone Name ID ... 3412 Bob 123 3834 Jonny 333 1248 Kate 634 1423 Tony 567 2567 Peter 435 3567 Alice 535 1548 Kerry 534 ... 다음과 같이 하면 된다: vi: s/^\(1.\)/\12/g emacs: ^\(1.\) replaced by \12 perl: perl -pe 's/^(1.)/${1}2/g' phonelist.txt Perl은 메모리 변수를 \1부터 \9까지 사용하므로 \12는 텅 비어있는 12번째 변수를 참조하고 있다. 이러한 문제를 해결하기 위하여 단순히 ${1}을 사용하면 된다. 위의 리스트는 아직도 약간 정렬이 잘못되어 있다. 어떻게 하면 고칠 수 있을까? 5번째 위치에 공백이 있는지 없는지 검사를 하고, 공백이 없으면 하나의 공백을 집어 넣으면 된다. vi: s/^\(....\) /\1 /g emacs: '^\(....\) ' replaced by '\1 ' perl: perl -pe 's/^(....) /${1} /g' phonelist.txt 이제 리스트는 다음과 같을 것이다. Phone Name ID ... 3412 Bob 123 3834 Jonny 333 12248 Kate 634 14223 Tony 567 2567 Peter 435 3567 Alice 535 15248 Kerry 534 ... 만약에 수작업을 하다가 줄의 시작에 몇 개의 공백을 입력했다면 공백들을 어떻게 제거할 수 있을까? Phone Name ID ... 3412 Bob 123 3834 Jonny 333 12248 Kate 634 14223 Tony 567 2567 Peter 435 3567 Alice 535 15248 Kerry 534 ... 소스 코드를 작성하면서 temp라는 변수와 temporary라는 변수를 사용하고 있다고 가정을 하자. 그런데 temp를 counter로 바꾸고자 한다. 단순히 temp가 counter로 바뀌면 temporary는 counterorary로 바뀌어 버린다. 정규 표현식은 이런 일을 할 수 있다. 단순히 temp([^o])를 counter\1로 바꾸면 된다. 즉, tempo가 아니라고 말해주면 된다. (크기의 경계을 두는 방법도 있지만, 우리는 이러한 방식의 패턴을 배우지 않았다.) Regular expressions can do it. Just replace temp([^o]) with counter\1. That is, temp and not the letter o. (An alternative solution would be to use boundaries but we have not discussed this kind of anchoring pattern.) 이 글을 읽고 흥미를 느꼈으면 하는 바램이다. 이제 당신은 좋아하는 에디터의 맨 페이지나, 메뉴얼을 보면서 더욱 자세하게 배워야 한다. 또한 위에서 말한 경계나 "or"를 나타내는 특별한 문자도 있다. 재미있게 보셨나요? 직접 해보세요..:) 번역 : 허정수 |
본 홈페이지는 Miguel Angel Sepulveda님께서 관리하고 있습니다. © Guido Socher 1998 LinuxFocus 1998 |