[루비 사용자 가이드] 정규식(Regular Expression)

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.28 02:08

좀 더 재미있는 프로그램을 살펴보도록 합시다. 이번에는 어떤 스트링이 간결한 패턴(pattern)으로 인코딩한 기준에 들어맞는지를 판단할 것입니다.

패턴에는 특별한 의미를 지니는 문자나 문자 조합이 있습니다. 그 중 몇 가지는 다음과 같죠:

[] 범위 지정 (예를 들어, [a-z]a부터 z까지의 모든 문자를 의미합니다.)
\w 숫자 혹은 알파벳; [0-9A-Za-z]와 같습니다.
\W 숫자도 알파벳도 아닌 문자
\s 화이트 스페이스; [ \t\n\r\f]과 같습니다.
\S 화이트 스페이스가 아닌 문자
\d 숫자; [0-9]와 같습니다.
\D 숫자가 아닌 문자
\b 백스페이스(0x08) (범위 지정 안에서만 유효합니다.)
\b 단어 경계 (범위 지정 밖에서만 유효합니다.)
\B 비(non) 단어 경계
* * 직전에 있는 패턴의 0번 혹은 그 이상의 반복
+ + 직전에 있는 패턴의 1번 이상의 반복
{m,n} {m,n} 직전에 있는 패턴의 최소 m번 이상, 최대 n번 이하의 반복
? 최대 1번 이하의 반복; {0,1}과 같습니다.
| | 좌우의 두 가지 패턴 중 한 가지와 일치
() 그룹으로 묶기

위에 설명한 이상한 패턴을 부르는 일반적인 이름은 정규식(regular expressions)입니다. 루비에서 - 펄과 마찬가지로 - 정규식은 큰따옴표가 아니라 슬래시로 둘러싸여 있습니다. 예전에 정규식을 본 적이 없는 분들에게는 그렇게 일반적이고 자주 보던(regular) 것으로 보이지는 않을 겁니다. 하지만, 정규식에 익숙해지도록 시간을 약간 투자하는것이 현명한 일입니다. 정규식은 여러분이 골머리를 앓는 일이 없도록 해주는(그리고 코드의 크기도 크게 줄여주는) 효율적인 표현 능력을 제공합니다. 패턴 매치나 검색이나 텍스트 스트링을 처리할 다른 작업이 필요할 때 유용하게 쓸 수 있습니다.

예를 들어 어떤 스트링이 다음 설명에 들어맞는지 체크하고 싶다고 합시다. "스트링이 소문자 f로 시작해서 정확히 한개의 대문자가 나온 다음, 선택적으로 여러개의 소문자가 아닌 문자가 올 수 있습니다." 만약 경험이 풍부한 C 프로그래머라면 이미 머리속으로 몇십줄의 코드를 짜고 있을겁니다. 그렇지 않나요? 그렇게 직접 문제를 해결하기로 한다고 해도 이런 문제를 풀 수 있을겁니다. 하지만 루비에서는 스트링을 /^f[A-Z][^a-z]*$/라는 정규식과 비교하기만 하면 됩니다.

"<과 > 사이에 있는 16진 수"는 어떻게 처리할까요? 문제 없습니다.

ruby> def chab(s)   # "<과 > 사이에 있는 16진 수"
    |    (s =~ /<0(x|X)(\d|[a-f]|[A-F])+>/) != nil
    | end
  nil
ruby> chab "Not this one."
  false
ruby> chab "Maybe this? {0x35}"    # 잘못된 괄호
  false
ruby> chab "Or this? <0x38z7e>"    # 사이비 16진 숫자
  false
ruby> chab "Okay, this: <0xfc0004>."
  true

정규식을 처음 보면 곤혹스러울 수 있지만, 조금만 써보면 생각을 너무나 경제적으로 표현할 수 있다는 점으로 인해 만족하게 됩니다.

여기 정규식을 테스트해 볼 수 있는 프로그램이 있습니다. regx.rb라는 이름으로 저장하고 "ruby regx.rb"를 커맨드라인에서 입력해서 실행해 보세요.

# ANSI 터미널에서 실행하세요!

st = "\033[7m"
en = "\033[m"

puts "Enter an empty string at any time to exit."

while true
  print "str> "; STDOUT.flush; str = gets.chop
  break if str.empty?
  print "pat> "; STDOUT.flush; pat = gets.chop
  break if pat.empty?
  re = Regexp.new(pat)
  puts str.gsub(re,"#{st}\\&#{en}")
end

이 프로그램은 두 번 입력을 요청합니다. 한 번은 스트링을 입력하는 것이고, 다른 한번은 정규식을 입력하는 것입니다. 스트링이 정규식과 매치되는지 검사를 해서 일치되는 부분은 역상(reverse)으로 표시됩니다. 세세한 부분에 너무 신경쓰지 마세요. 곧 이 코드에 대해 분석하게 됩니다.

str> foobar
pat> ^fo+
foobar
~~~

위에서 빨간색으로 표시된 것이 프로그램의 출력에서 역상으로 나타납니다. "~~~"라인은 텍스트 기반의 브라우저를 사용하는 사용자에게 위치를 보여주기 위한 것입니다.

몇 가지 입력을 더 테스트해봅시다.

str> abc012dbcd555
pat> \d
abc012dbcd555
   ~~~    ~~~

만약 이 결과가 여러분을 놀라게 했다면 이 페이지의 맨 처음에 보여드렸던 테이블을 살펴봐 주세요. \d는 문자 d와는 아무 관계가 없고 숫자 하나와 매치됩니다.

만약 패턴과 매치되는 방법이 하나 이상 있는 경우엔 어떻게 될까요?

str> foozboozer
pat> f.*z
foozboozer
~~~~~~~~

fooz 대신에 foozbooz가 매치됩니다. 정규식 매치는 가장 긴 매치 가능한 서브스트링에 대해 이루어집니다.

다음은 콜론으로 구분된 시간 필드를 찾아내는 패턴입니다.

str> Wed Feb  7 08:58:04 JST 1996
pat> [0-9]+:[0-9]+(:[0-9]+)?
Wed Feb  7 08:58:04 JST 1996
           ~~~~~~~~

"=~"는 정규식에 대해 매치하는 연산자입니다; 이 연산자는 스트링과 패턴이 매치가 된 위치를 반환하거나, 매치가 이루어질 수 없으면 nil을 반환합니다.

ruby> "abcdef" =~ /d/
   3
ruby> "aaaaaa" =~ /d/
   nil


Trackbacks 0 : Comments 0

Write a comment