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.compile은 new의 별칭이에요. 옵션은 문자열('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.new의 timeout 키워드를 쓰세요.
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
object가 self와 같은 패턴·플래그·인코딩을 가진 다른 정규식이면 true를 반환해요.
/foo/ == Regexp.new('foo') # => true
/foo/ == /foo/i # => false
self === string → true or false
self가 string에서 일치를 찾으면 true를 반환해요. case 문에서 정규식을 쓸 때 호출돼요.
/^[A-Z]*$/ === 'HELLO' # => true
self =~ string → integer or nil
self와 string의 첫 일치 위치(문자 단위 정수)를 반환하고, 없으면 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_json과 Regexp.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 문서를 참고하세요.