Regexp 클래스

Regexp 클래스

Regexp(정규식, regular expression)는 문자열 안에서 어떤 패턴(match pattern)이 들어있는지 검사할 때 쓰는 클래스예요. 보통 /.../ 리터럴 형태로 만들고, 대상 문자열에 적용하면 그 패턴과 일치하는 부분을 찾아줘요.

출처: Ruby 4.0 API

본문

정규식은 크게 몇 가지 쓰임이 있어요.

  • 패턴에 맞는 부분 문자열(substring) 추출하기
  • 문자열이 어떤 패턴과 일치하는지 판단하기
  • 다른 클래스·모듈의 메서드에 인자로 넘기기(문자열 대신 훨씬 강력한 정규식을 줄 수 있어요)
re = /red/
re.match?('redirect') # => true   # 대상 시작 부분 일치
re.match?('bored')    # => true   # 대상 끝 부분 일치
re.match?('credit')   # => true   # 대상 중간 일치
re.match?('foo')      # => false  # 불일치

정규식 만들기 (Creating a Regexp)

정규식은 리터럴이나 Regexp.new로 만들 수 있어요.

# 슬래시 리터럴 — 가장 흔한 방법.
/foo/ # => /foo/

# %r 리터럴 — 슬래시를 이스케이프하기 싫을 때 유용.
%r/name\/value pair/ # => /name\/value pair/
%r:name/value pair:  # => /name\/value pair/
%r[foo] # => /foo/
%r{foo} # => /foo/

Regexp.new('foo') # => /foo/

일치 방식 (match, =~, match?)

정규식과 문자열을 매칭하는 방법은 세 가지가 있어요.

  • match: MatchData 객체를 반환하거나 nil을 반환해요. 글로벌 변수도 설정돼요.
  • =~: 일치하면 위치(정수)를, 아니면 nil을 반환해요. 글로벌 변수도 설정돼요.
  • match?: true/false만 반환하고 글로벌 변수는 건드리지 않아요(그래서 더 빨라요).
'food'.match(/foo/) # => #<MatchData "foo">
/bar/ =~ 'foo bar'   # => 4
'food'.match?(/foo/) # => true

소스와 하위 표현식 (Sources and Subexpressions)

정규식 소스에는 여러 하위 표현식들이 있어서 패턴을 아주 유연하게 만들 수 있어요. 주요 개념만 정리할게요.

메타문자 (Metacharacters)

특정 문맥에서 특별한 의미를 갖는 문자들이 있어요.

. ? - + * ^ \ | $ ( ) [ ] { }

메타문자를 문자 그대로 매칭하려면 백슬래시로 이스케이프해요. Regexp.escape가 문자열을 이스케이프해 줘요.

/o+/.match('foo')          # => #<MatchData "oo">   # o가 1개 이상
/o\+/.match('foo')         # => nil                  # 'o+' 그대로
Regexp.escape('.?-+*^\|$()[]{}')
# => "\.\?-\+\*\^\\\|$\(\)\[\]\{\}"

문자 클래스 (Character Classes)

대괄호로 감싸서 특정 문자 집합을 지정해요. ^로 시작하면 그 집합을 제외한 나머지를 매칭하고, -로 범위를 지정할 수 있어요.

re = /B[aeiou]rd/
re.match('Bird') # => #<MatchData "Bird">
re.match('Byrd') # => nil

/[a-f]/.match('foo')        # => #<MatchData "f">
/[^a-eg-z]/.match('f')      # => #<MatchData "f">
/[a-w&&[^c-g]z]/            # [a-w] AND ([^c-g] OR z) — 집합 교집합

축약 문자 클래스 (Shorthand Character Classes)

자주 쓰는 클래스에는 축약형이 있어요.

  • /./ : 개행을 제외한 모든 문자. /./m은 개행 포함.
  • /\w/ : 단어 문자 ([a-zA-Z0-9_]), /\W/는 그 반대.
  • /\d/ : 숫자 ([0-9]), /\D/는 그 반대.
  • /\h/ : 16진수 문자 ([0-9a-fA-F]), /\H/는 그 반대.
  • /\s/ : 공백 문자, /\S/는 그 반대.
  • /\R/ : 줄바꿈(linebreak) 문자, 플랫폼 무관하게 매칭해요.
/\w/.match(' foo') # => #<MatchData "f">
/\d/.match('THX1138') # => #<MatchData "1">
/\s/.match('foo bar') # => #<MatchData " ">
/\R/.match("\r\n")    # => #<MatchData "\r\n">   # CRLF

앵커 (Anchors)

앵커는 문자열의 위치를 지정하는 메타시퀀스예요. 크게 경계(boundary)·전방탐색/후방탐색(lookaround)·매치 리셋으로 나뉘어요.

# 경계 앵커
/^bar/.match("foo\nbar")  # => #<MatchData "bar">  # 줄 시작
/bar$/.match("foo\nbar")  # => #<MatchData "bar">  # 줄 끝
/\Afoo/.match('foo bar')  # => #<MatchData "foo">  # 문자열 시작
/foo\z/.match('bar foo')  # => #<MatchData "foo">  # 문자열 끝
/foo\b/.match('foo bar')  # => #<MatchData "foo">  # 단어 경계
/foo\B/.match('foobar')   # => #<MatchData "foo">  # 비-단어 경계

# 전방/후방 탐색 — 매칭에 포함되지 않아요.
/(?<=<b>)\w+(?=<\/b>)/.match("Fortune favors the <b>bold</b>.")
# => #<MatchData "bold">

# 매치 리셋 — \K 앞의 일치는 결과에서 제외.
/ab\Kc/.match('abc')    # => #<MatchData "c">

전방탐색은 (?=pat)(긍정), (?!pat)(부정), 후방탐색은 (?<=pat)(긍정), (?<!pat)(부정)이에요. 후방탐색의 패턴은 고정 폭이어야 하지만, 톱레벨 대안들은 길이가 달라도 괜찮아요.

대안 (Alternation)

| 메타문자는 여러 하위 표현식 중 하나가 매칭되는 대안을 표현해요.

re = /(a|b|c|d)/
re.match('shazam') # => #<MatchData "a" 1:"a">
re.match('cold')   # => #<MatchData "c" 1:"c">

Regexp.union은 이런 대안 정규식을 쉽게 만들어 줘요.

수량자 (Quantifiers)

수량자는 앞의 하위 표현식이 몇 번 매칭되는지 지정해요.

  • * : 0회 이상
  • + : 1회 이상
  • ? : 0회 또는 1회
  • {n} : 정확히 n회
  • {min,} : min회 이상
  • {,max} : max회 이하
  • {min,max} : min 이상 max 이하
/\w*/.match('xyz')  # => #<MatchData "xyz">
/\w+/.match('x')    # => #<MatchData "x">
/\w{2}/.match('xyz') # => #<MatchData "xy">

수량자 매칭은 탐욕적(greedy), 게으른(lazy), 소유적(possessive) 세 방식이 있어요. 게으른 수량자는 *?, +?, ?? 같이 뒤에 ?를 붙이고, 소유적 수량자는 *+, ++, ?+ 같이 뒤에 +를 붙여요.

그룹과 캡처 (Groups and Captures)

(...)는 그룹을 만들어 일치한 부분을 캡처해요.

re = /(\d\d\d\d)-(\d\d)-(\d\d)/
re.match('1943-02-04')
# => #<MatchData "1943-02-04" 1:"1943" 2:"02" 3:"04">
  • (?:...) : 비캡처 그룹. 캡처하지 않아요.
  • \1, \2 : 역참조(backreference).
  • (?<name>...) : 이름 있는 캡처.
  • (?>...) : 원자 그룹(atomic grouping).
  • \g<name> : 부분 표현식 호출(subexpression call).

모드 (Modes)

정규식에는 동작을 바꾸는 몇 가지 모드가 있어요.

  • 대소문자 무시(Ignore Case) : (?i:...) 로 특정 부분에만 적용할 수도 있어요.
  • 멀티라인(Multiline) : .이 개행도 포함하도록 (/.../m).
  • 확장(Extended) : 공백과 주석 허용 (/.../x).
  • 보간(Interpolation) : 소스 안에 #{}로 표현식을 넣을 수 있어요.

타임아웃 (Timeouts)

Regexp 매칭에는 타임아웃을 설정할 수 있어요. 까다로운(대량의 역추적이 필요한) 패턴은 처리 시간이 길어질 수 있는데, 실수로 오래 걸리는 것을 방지하려고 사용해요. 전역 설정은 Regexp.timeout=으로, 개별 정규식에는 Regexp.new(..., timeout: n)으로 설정해요.

상수 (Constants)

  • IGNORECASE: 대소문자 무시 플래그(1)
  • EXTENDED: 확장 모드 플래그(2)
  • MULTILINE: 멀티라인 플래그(4)
  • FIXEDENCODING: 고정 인코딩 플래그(16)
  • NOENCODING: 인코딩 없는 바이너리 플래그(32)

공개 클래스 메서드 (Public Class Methods)

new(string, options = 0, timeout: nil) → regexp / compile(*args)

문자열과 옵션으로 새 정규식을 만들어요. Regexp.compilenew의 별칭이에요. 옵션은 문자열('im')이나 상수의 비트 OR로 줄 수 있고, timeout 키워드로 타임아웃을 정할 수 있어요. 정규식을 인자로 주면 소스·옵션·타임아웃을 그대로 가져와요.

r = Regexp.new('foo')            # => /foo/
Regexp.new('foo', 'im')          # => /foo/im
Regexp.new('foo', Regexp::IGNORECASE) # => /foo/i
r2 = Regexp.new(r, timeout: 3.14) # => /foo/m

escape(string) → new_string

정규식에서 특별한 의미를 가진 문자들을 이스케이프한 새 문자열을 반환해요.

Regexp.escape('.*?{}') # => "\\.\\*\\?\\{\\}"

last_match → matchdata or nil / last_match(n) / last_match(name)

인자가 없으면 최근 패턴 매칭의 결과인 $~의 값을 반환해요. 정수 인자가 있으면 그 필드를, 문자열·심볼 인자가 있으면 이름 있는 캡처의 값을 반환해요.

/c(.)t/ =~ 'cat'  # => 0
Regexp.last_match # => #<MatchData "cat" 1:"a">
Regexp.last_match(1) # => "a"
Regexp.last_match(-1) # => "a"

linear_time?(re) → true or false

re에 대한 매칭이 입력 문자열 길이에 대해 선형 시간에 끝날 수 있으면 true를 반환해요. 이는 인자 정규식의 속성이 아니라 Ruby 인터프리터의 속성이라는 점에 주의하세요. 같은 정규식이라도 사용하는 ruby 바이너리에 따라 선형 시간 여부가 달라질 수 있고, 반환값의 전진·후진 호환성은 보장되지 않아요.

Regexp.linear_time?(/re/) # => true

timeout → float or nil / timeout = float or nil

Regexp 매칭의 현재 기본 타임아웃 간격(초)을 반환해요. nil은 기본 타임아웃 설정이 없음을 뜻해요. setter는 그 기본값을 설정하는데, 프로세스 전역 설정이에요. 개별 정규식마다 다르게 하려면 Regexp.newtimeout 키워드를 쓰세요.

Regexp.timeout = 1
/^a*b?a*$/ =~ "a" * 100000 + "x" #=> regexp match timeout (RuntimeError)

try_convert(object) → regexp or nil

object가 정규식이면 그대로 반환해요. :to_regexp에 응답하면 object.to_regexp를 호출해 결과를 반환해요. 응답하지 않으면 nil을 반환해요. to_regexp가 정규식을 반환하지 않으면 예외를 발생시켜요.

Regexp.try_convert(/re/) # => /re/
Regexp.try_convert('re') # => nil

union(*patterns) → regexp

주어진 패턴들의 합집합(union)인 새 정규식을 반환해요. 문자열 패턴은 Regexp.new(pattern)으로 만들고, 정규식 패턴은 플래그를 포함해 그대로 써요. 인자가 없으면 /(?!)/을 반환해요.

Regexp.union(%w[cat dog])    # => /cat|dog/
Regexp.union(/foo/i, /bar/m) # => /(?i-mx:foo)|(?m-ix:bar)/
Regexp.union                 # => /(?!)/

json_create(object)

as_json을 참고하세요. JSON에서 Regexp 객체를 복원해요.

공개 인스턴스 메서드 (Public Instance Methods)

self == object → true or false

objectself와 같은 패턴·플래그·인코딩을 가진 다른 정규식이면 true를 반환해요.

/foo/ == Regexp.new('foo') # => true
/foo/ == /foo/i            # => false

self === string → true or false

selfstring에서 일치를 찾으면 true를 반환해요. case 문에서 정규식을 쓸 때 호출돼요.

/^[A-Z]*$/ === 'HELLO' # => true

self =~ string → integer or nil

selfstring의 첫 일치 위치(문자 단위 정수)를 반환하고, 없으면 nil을 반환해요. Regexp 글로벌 변수도 설정해요. 정규식 리터럴이 왼쪽에 있고 보간이 없으면 이름 있는 캡처를 같은 이름의 지역 변수에 할당해요.

/at/ =~ 'input data' # => 7
/(?<lhs>\w+)\s*=\s*(?<rhs>\w+)/ =~ '  x = y  '
p lhs # => "x"
p rhs # => "y"

~ rxp → integer or nil

rxp =~ $_와 동일해요. $_(마지막으로 읽은 줄)에서 일치 위치를 반환해요.

$_ = "input data"
~ /at/ # => 7

as_json(*)

Regexp#as_jsonRegexp.json_create 메서드는 Regexp 객체를 직렬화/역직렬화하는 데 쓸 수 있어요. Marshal을 참고하세요.

require 'json/add/regexp'
x = /foo/.as_json      # => {"json_class"=>"Regexp", "o"=>0, "s"=>"foo"}
Regexp.json_create(x)  # => /foo/

casefold? → true or false

self에 대소문자 무시 플래그가 설정돼 있으면 true를 반환해요.

/a/.casefold?      # => false
/a/i.casefold?     # => true
/(?i:a)/.casefold? # => false

encoding → encoding

self의 인코딩을 나타내는 Encoding 객체를 반환해요.

eql?(other)

두 정규식이 같은 소스·옵션·인코딩이면 true를 반환해요. ==와 관련돼요.

fixed_encoding? → true or false

self가 ASCII 호환 인코딩의 어떤 문자열에도 적용될 수 있으면 false, 그렇지 않으면 true를 반환해요.

/a/.fixed_encoding?    # => false
/a/u.fixed_encoding?   # => true
/\u{6666}/.fixed_encoding? # => true

hash → integer

self의 정수 해시 값을 반환해요.

inspect → string

self의 보기 좋은 문자열 표현을 반환해요.

/ab+c/ix.inspect # => "/ab+c/ix"

match(string, offset = 0) → matchdata or nil

string에서 일치를 설명하는 MatchData 객체를 반환하고, 없으면 nil을 반환해요. 검색은 offset 문자 위치에서 시작해요. 블록이 주어지면 일치할 때만 블록을 호출하고 블록의 값을 반환해요.

/abra/.match('abracadabra')     # => #<MatchData "abra">
/abra/.match('abracadabra', 4)  # => #<MatchData "abra">
/abra/.match('abracadabra', 8)  # => nil

match?(string, offset = 0) → true or false

정규식이 일치하는지 true/false로 반환해요. $~나 관련 변수를 갱신하지 않아서 가벼워요. 두 번째 인자로 검색 시작 위치를 지정할 수 있어요.

/R.../.match?("Ruby")    # => true
/R.../.match?("Ruby", 1) # => false

named_captures → hash

self의 이름 있는 캡처를 나타내는 해시를 반환해요. 각 키는 캡처 이름이고, 각 값은 그 이름의 정수 인덱스 배열이에요.

/(?<foo>.)(?<bar>.)/.named_captures # => {"foo"=>[1], "bar"=>[2]}
/(?<foo>.)(?<foo>.)/.named_captures # => {"foo"=>[1, 2]}

names → array_of_names

캡처 이름들의 배열을 반환해요.

/(?<foo>.)(?<bar>.)(?<baz>.)/.names # => ["foo", "bar", "baz"]

options → integer

self에 설정된 옵션을 보여주는 정수를 반환해요. 비트 값은 IGNORECASE(1), EXTENDED(2), MULTILINE(4)이에요.

/foo/.options    # => 0
/foo/i.options   # => 1
/foo/mix.options # => 7

source → string

self의 원본 소스 문자열을 반환해요.

/ab+c/ix.source # => "ab+c"

timeout → float or nil

Regexp 매칭의 타임아웃 간격(초)을 반환해요. nil은 기본 타임아웃 설정이 없음을 뜻해요. 이 설정은 객체별(per-object)이고, 객체별 설정이 있으면 Regexp.timeout=의 전역 설정은 무시돼요.

re = Regexp.new("^a*b?a*$", timeout: 1)
re.timeout #=> 1.0

to_json(*args)

self를 나타내는 JSON 문자열을 반환해요.

require 'json/add/regexp'
puts /foo/.to_json
# {"json_class":"Regexp","o":0,"s":"foo"}

to_s → string

self의 옵션과 문자열을 보여주는 문자열을 반환해요. 반환 문자열은 Regexp.new의 인자나 보간 텍스트로 쓸 수 있어요.

r0 = /ab+c/ix
s0 = r0.to_s # => "(?ix-m:ab+c)"
r1 = Regexp.new(s0) # => /(?ix-m:ab+c)/

더 알아보기

  • 정규식 사용법과 상세한 하위 표현식 문법은 Ruby 4.0 Regexp 공식 문서에서 더 자세히 다뤄요.
  • match가 반환하는 일치 결과 객체는 MatchData 문서를 참고하세요.