Regex 모듈
Regex 모듈
Elixir를 위한 정규 표현식(regular expression)을 제공하는 모듈이에요. Regex는 PCRE(Perl Compatible Regular Expressions)를 기반으로 하며 Erlang의 :re 모듈 위에 구축되어 있습니다. 더 자세한 정보는 :re 모듈 문서에서 찾을 수 있어요.
출처: Regex
본문
Elixir의 정규 표현식은 ~r 시그널(sigil, sigil_r/2 참고)로 만들 수 있습니다.
# A simple regular expression that matches foo anywhere in the string
~r/foo/
# A regular expression with case insensitive and Unicode options
~r/foo/iu
Regex는 내부적으로 Regex 구조체로 표현됩니다. 따라서 %Regex{}는 매칭이 필요할 때마다 쓸 수 있어요. 구조체의 모든 필드는 비공개임을 명심하세요. 정규식은 컴파일되므로 같은 소스에서 만든 두 정규식이 같다는 보장이 없어요. 예를 들면:
~r/(?<foo>.)(?<bar>.)/ == ~r/(?<foo>.)(?<bar>.)/
이는 머신, 엔디언(endianness), 사용 가능한 최적화 등에 따라 true나 false를 반환할 수 있습니다. 하지만 컴파일된 정규식의 소스는 source 필드에 접근해 가져오고, 그것들을 직접 비교할 수는 있어요.
~r/(?<foo>.)(?<bar>.)/.source == ~r/(?<foo>.)(?<bar>.)/.source
이스케이프(Escapes)
이스케이프 시퀀스는 두 범주로 나뉩니다.
인쇄되지 않는 문자(Non-printing characters)
\a- Alarm, 즉 BEL 문자 (hex 07)\e- Escape (hex 1B)\f- Form feed (hex 0C)\n- Line feed (hex 0A)\r- Carriage return (hex 0D)\t- Tab (hex 09)\xhh- hex 코드 hh의 문자\x{hhh..}- hex 코드 hhh..의 문자
\u와 \U는 지원되지 않습니다. 8진수용 \ddd 같은 다른 이스케이프 시퀀스는 지원되지만 권장되지는 않아요.
일반 문자 타입(Generic character types)
\d- 모든 십진 숫자\D- 십진 숫자가 아닌 모든 문자\h- 모든 가로 공백 문자\H- 가로 공백 문자가 아닌 모든 문자\s- 모든 공백 문자\S- 공백 문자가 아닌 모든 문자\v- 모든 세로 공백 문자\V- 세로 공백 문자가 아닌 모든 문자\w- 모든 "단어(word)" 문자\W- 모든 "비-단어" 문자
수정자(Modifiers)
Regex를 만들 때 사용 가능한 수정자는 다음과 같습니다.
:unicode(u) —\p같은 Unicode 특화 패턴을 활성화하고\w,\W,\s같은 문자 클래스가 Unicode와도 매치되게 합니다(아래 "Character classes"의 예시 참고). 매치 시 유효한 Unicode 문자열이 주어질 것을 기대합니다:caseless(i) — 대소문자 무시 추가:dotall(s) — dot이 newline에 매치되게 하고 newline을(*ANYCRLF)로 설정합니다.:re문서에 설명된 newline 설정은 정규식 패턴을 다음으로 시작해서 덮어쓸 수 있어요:(*CR)- carriage return(*LF)- line feed(*CRLF)- carriage return, 그다음 line feed(*ANYCRLF)- 위 셋 중 아무거나(*ANY)- 모든 Unicode newline 시퀀스(*NUL)- NUL 문자(바이너리 0) (Erlang/OTP 28부터)
:multiline(m) —^와$가 각 줄의 시작과 끝을 나타내게 함. 문자열의 끝이나 시작을 매치하려면\A와\z를 쓰세요:extended(x) — 이스케이프되거나[..]안에 있지 않는 한 공백 문자를 무시하고,#이 주석을 구분하게 함:firstline(f) — 앵커되지 않은(un-anchored) 패턴이 첫 newline 전이나 그 위치에서 매치되게 하지만, 매치된 텍스트는 newline을 넘어 계속될 수 있음:ungreedy(U) — 정규식의 "greediness"를 뒤집음 (이전r옵션은U를 위해 deprecated):export(E) (Elixir 1.19.3부터) — 노드 간 공유하거나 config로 전달할 수 있는 export된 패턴을 사용하지만, 실행할 때마다 다시 import하는 런타임 오버헤드가 있음. 이 수정자는 Erlang/OTP 28부터 효과가 있고, 이전 버전에서는 무시됩니다(즉~r/foo/E == ~r/foo/). 그 버전들에서는 패턴이 공유되기 위해 export될 필요도 없고 그럴 수도 없기 때문이에요.
캡처(Captures)
이 모듈의 많은 함수는 :capture 옵션으로 regex 매치에서 무엇을 캡처할지 처리합니다. 지원되는 값은 다음과 같습니다.
:all— 완전한 매칭 문자열을 포함한 모든 캡처 하위패턴 (기본값):first— 첫 번째 캡처 하위패턴만, 항상 문자열의 완전한 매칭 부분. 명시적으로 캡처된 하위패턴은 모두 버려짐:all_but_first— 첫 번째를 제외한 모든 매칭 하위패턴, 즉 명시적으로 캡처된 모든 하위패턴이지만 문자열의 완전한 매칭 부분은 아님:none— 매칭 하위패턴을 전혀 반환하지 않음:all_names— 하위패턴의 이름 순서대로 알파벳순으로 정렬된 리스트로 Regex의 모든 이름 있는 하위패턴 매치를 캡처list(binary | atom)— 캡처할 이름 있는 캡처 리스트
문자 클래스(Character classes)
Regex는 몇 가지 내장 이름 있는 문자 클래스를 지원합니다. 이것들은 그룹 안에서 [: :]로 클래스 이름을 감싸서 사용해요. 예를 들면:
iex> String.match?("123", ~r/^[[:alnum:]]+$/)
true
iex> String.match?("123 456", ~r/^[[:alnum:][:blank:]]+$/)
true
지원되는 클래스 이름은 다음과 같습니다.
- alnum - 문자와 숫자
- alpha - 문자
- blank - 공백 또는 탭만
- cntrl - 제어 문자
- digit - 십진 숫자 (
\d와 같음) - graph - 공백을 제외한 인쇄 문자
- lower - 소문자
- print - 공백을 포함한 인쇄 문자
- punct - 문자, 숫자, 공백을 제외한 인쇄 문자
- space - 공백 (
\s와 같음) - upper - 대문자
- word - "단어" 문자 (
\w와 같음) - xdigit - 16진수 숫자
ascii라는 다른 문자 클래스도 있는데, POSIX가 지정한 0-127 범위 대신 라틴-1 문자를 잘못 매치합니다. 다른 클래스의 동작을 바꾸지 않고는 고칠 수 없으므로, 그 범위는 [\0-\x7f]로 매치할 것을 권장합니다.
그 클래스들의 동작은 Unicode 및 다른 수정자에 따라 바뀔 수 있음에 유의하세요.
iex> String.match?("josé", ~r/^[[:lower:]]+$/)
false
iex> String.match?("josé", ~r/^[[:lower:]]+$/u)
true
iex> Regex.replace(~r/\s/, "Unicode\u00A0spaces", "-")
"Unicode spaces"
iex> Regex.replace(~r/\s/u, "Unicode\u00A0spaces", "-")
"Unicode-spaces"
타입(Types)
@type capture_opts() :: [
return: :binary | :index,
capture:
:all | :first | :all_but_first | :none | :all_names | [binary() | atom()],
offset: non_neg_integer()
]
@type named_captures_opts() :: [return: :binary | :index, offset: non_neg_integer()]
@type split_opts() :: [
parts: pos_integer() | :infinity,
trim: boolean(),
on:
:first | :all | :all_but_first | :none | :all_names | [atom() | integer()],
include_captures: boolean()
]
@type t() :: %Regex{opts: [term()], re_pattern: term(), source: binary()}
capture_opts()— 매치를 캡처하는 regex 함수의 옵션split_opts()—split/3의 옵션
함수(Functions)
compile(source, opts \ "")
@spec compile(binary(), binary() | [term()]) :: {:ok, t()} | {:error, term()}
정규 표현식을 컴파일합니다. 주어진 옵션은 ~r(시그널 sigil_r/2 참고) 시그널에 주어지는 것과 같은 정규식 옵션을 나타내는 문자들이 담긴 바이너리이거나, Erlang :re 모듈이 기대하는 옵션 리스트일 수 있어요. 성공하면 {:ok, regex}를, 아니면 {:error, reason}을 반환합니다.
Regex.compile("foo")
#=> {:ok, ~r/foo/}
Regex.compile("foo", "i")
#=> {:ok, ~r/foo/i}
Regex.compile("*foo")
#=> {:error, {~c"quantifier does not follow a repeatable item", 0}}
compile!(source, options \ "")
@spec compile!(binary(), binary() | [term()]) :: t()
정규 표현식을 컴파일하고, 오류가 있으면 Regex.CompileError를 던집니다.
escape(string)
@spec escape(String.t()) :: String.t()
regex에서 리터럴로 매치되도록 문자열을 이스케이프합니다.
iex> Regex.escape(".")
"\\."
iex> Regex.escape("\\what if")
"\\\\what\\ if"
import(regex) (1.20.0부터)
@spec import(t()) :: t()
export된 regex를 import하며, 아니면 regex를 그대로 반환합니다. 즉 노드 간에 보내거나 config로 전달하는 능력은 잃지만, 실행할 때마다 즉석에서 import할 필요가 없으므로 더 빨라집니다. Export된 regex는 OTP 28에만 존재하므로 이전 버전에서는 효과가 없어요.
Regex.import(~r/foo/E)
~r/foo/
Regex.import(~r/foo/)
~r/foo/
match?(regex, string)
@spec match?(t(), String.t()) :: boolean()
매치가 있었는지 여부를 나타내는 불리언을 반환합니다.
iex> Regex.match?(~r/foo/, "foo")
true
iex> Regex.match?(~r/foo/, "bar")
false
Elixir는 또한 정규식과 문자열을 테스트하는 대안으로 텍스트 기반 매치 연산자 =~/2와 함수 String.match?/2를 제공합니다.
named_captures(regex, string, options \ [])
@spec named_captures(t(), String.t(), named_captures_opts()) :: map() | nil
주어진 캡처를 맵으로 반환하거나, 캡처가 없으면 nil을 반환합니다.
옵션:
:return—:index로 설정하면 바이트 인덱스와 매치 길이를 반환. 기본값:binary.:offset— (v1.12.0부터) 주어진 문자열에서 매치를 시작할 시작 오프셋 지정. 기본값0.
iex> Regex.named_captures(~r/c(?<foo>d)/, "abcd")
%{"foo" => "d"}
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "abcd")
%{"bar" => "d", "foo" => "b"}
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "efgh")
nil
이름 있는 캡처에서 인덱스를 가져올 수도 있어요. 이름 있는 캡처가 매치됐는지 알고 싶을 때 특히 유용합니다.
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)?/, "abc", return: :index)
%{"bar" => {-1, 0}, "foo" => {1, 1}}
그런 다음 binary_part/3을 써서 주어진 문자열에서 관련 부분을 가져올 수 있어요.
names(regex)
@spec names(t()) :: [String.t()]
regex 안의 이름 리스트를 반환합니다.
iex> Regex.names(~r/(?<foo>bar)/)
["foo"]
opts(regex)
@spec opts(t()) :: [term()]
regex 옵션을 반환합니다. Regex.compile/2의 문서를 참고하세요.
iex> Regex.opts(~r/foo/m)
[:multiline]
iex> Regex.opts(Regex.compile!("foo", [:caseless]))
[:caseless]
re_pattern(regex)
@spec re_pattern(t()) :: term()
정규 표현식의 기저 re_pattern을 반환합니다.
recompile(regex) (1.4.0부터, deprecated)
이 함수는 deprecated입니다. 제거될 수 있으며 효과가 없습니다. 정규 표현식의 버전을 확인하고 버전 불일치 시 다시 컴파일합니다.
recompile!(regex) (1.4.0부터, deprecated)
이 함수는 deprecated입니다. 제거될 수 있으며 효과가 없습니다. 기존 정규 표현식을 다시 컴파일하고, 오류가 있으면 Regex.CompileError를 던집니다.
replace(regex, string, replacement, options \ [])
@spec replace(t(), String.t(), String.t() | (... -> String.t()), [
{:global, boolean()}
]) :: String.t()
regex, 바이너리, replacement(replacement 문자열 또는 문자열을 반환하는 함수)를 받아, 모든 매치가 replacement로 교체된 새 바이너리를 반환합니다. replacement가 문자열이면 \N 또는 \g{N}으로 특정 캡처에 접근할 수 있는데, N은 캡처 번호입니다. \0을 쓰면 전체 매치가 삽입됩니다. regex에서 백슬래시는 이스케이프되어야 하므로 실제로는 \\N과 \\g{N}을 써야 합니다. replacement가 함수이면 특정 캡처도 허용합니다. 함수는 arity N을 가질 수 있고 각 인자는 캡처에 매핑되며, 첫 인자가 전체 매치입니다. 함수가 발견된 캡처보다 많은 인자를 기대하면 나머지 인자는 ""를 받습니다.
옵션:
:global—false이면 첫 번째 발생만 교체 (기본값true)
iex> Regex.replace(~r/d/, "abc", "d")
"abc"
iex> Regex.replace(~r/b/, "abc", "d")
"adc"
iex> Regex.replace(~r/b/, "abc", "[\\0]")
"a[b]c"
iex> Regex.replace(~r/a(b|d)c/, "abcadc", "[\\1]")
"[b][d]"
iex> Regex.replace(~r/\.(\d)$/, "500.5", ".\\g{1}0")
"500.50"
iex> Regex.replace(~r/a(b|d)c/, "abcadc", fn _, x -> "[#{x}]" end)
"[b][d]"
iex> Regex.replace(~r/(\w+)@(\w+).(\w+)/, "[email protected]", fn _full, _c1, _c2, c3 -> "TLD: #{c3}" end)
"TLD: com"
iex> Regex.replace(~r/a/, "abcadc", "A", global: false)
"Abcadc"
run(regex, string, options \ [])
@spec run(t(), binary(), capture_opts()) ::
nil | [binary()] | [{integer(), integer()}]
주어진 문자열에 대해 첫 매치까지 정규 표현식을 실행합니다. 모든 캡처가 담긴 리스트를 반환하거나, 매치가 없으면 nil을 반환합니다.
옵션:
:return—:index로 설정하면 바이트 인덱스와 매치 길이 반환. 기본값:binary.:capture— 결과에서 무엇을 캡처할지. 가능한 캡처 값은 "Captures" 섹션 참고.:offset— (v1.12.0부터) 주어진 문자열에서 매치를 시작할 시작 오프셋 지정. 기본값0.
iex> Regex.run(~r/c(d)/, "abcd")
["cd", "d"]
iex> Regex.run(~r/e/, "abcd")
nil
iex> Regex.run(~r/c(d)/, "abcd", return: :index)
[{2, 2}, {3, 1}]
iex> Regex.run(~r/c(d)/, "abcd", capture: :first)
["cd"]
iex> Regex.run(~r/c(?<foo>d)/, "abcd", capture: ["foo", "bar"])
["d", ""]
scan(regex, string, options \ [])
@spec scan(t(), String.t(), capture_opts()) ::
[[String.t()]] | [[{integer(), integer()}]]
run/3와 같지만 정규 표현식의 모든 겹치지 않는 매치를 반환합니다. 리스트의 리스트가 반환되는데, 바깥 리스트의 각 항목은 매치를, 안쪽 리스트의 각 항목은 캡처된 내용을 나타냅니다.
옵션:
:return—:index로 설정하면 바이트 인덱스와 매치 길이 반환. 기본값:binary.:capture— 결과에서 무엇을 캡처할지. "Captures" 섹션 참고.:offset— (v1.12.0부터) 주어진 문자열에서 매치를 시작할 시작 오프셋 지정. 기본값0.
iex> Regex.scan(~r/c(d|e)/, "abcd abce")
[["cd", "d"], ["ce", "e"]]
iex> Regex.scan(~r/c(?:d|e)/, "abcd abce")
[["cd"], ["ce"]]
iex> Regex.scan(~r/e/, "abcd")
[]
iex> Regex.scan(~r/ab|bc|cd/, "abcd")
[["ab"], ["cd"]]
iex> Regex.scan(~r/ab|bc|cd/, "abbccd")
[["ab"], ["bc"], ["cd"]]
iex> Regex.scan(~r/\p{Sc}/u, "$, £, and €")
[["$"], ["£"], ["€"]]
iex> Regex.scan(~r/=+/, "=ü†ƒ8===", return: :index)
[[{0, 1}], [{9, 3}]]
iex> Regex.scan(~r/c(d|e)/, "abcd abce", capture: :first)
[["cd"], ["ce"]]
source(regex)
@spec source(t()) :: String.t()
regex 소스를 바이너리로 반환합니다.
iex> Regex.source(~r/foo/)
"foo"
split(regex, string, options \ [])
@spec split(t(), String.t(), split_opts()) :: [String.t()]
주어진 대상을 주어진 패턴과 주어진 부분 수에 따라 나눕니다.
옵션:
:parts— 지정하면 문자열을 주어진 부분 수로 나눔. 지정하지 않으면:parts기본값은:infinity로, 주어진 패턴에 기반해 최대한 많은 부분으로 문자열을 나눔.:trim—true이면 결과에서 빈 문자열("")을 제거. 기본값false.:on— 문자열을 어떤 캡처로 나눌지, 어떤 순서로 할지 지정. 기본값:first는 regex 안의 캡처가 분할 과정에 영향을 주지 않는다는 뜻. 가능한 캡처 값은 "Captures" 섹션 참고.:include_captures—true이면 결과에 정규 표현식의 매치를 포함.:parts옵션과 결합하면 그 매치들은 최대 부분 수로 세지 않음. 기본값false.
iex> Regex.split(~r/-/, "a-b-c")
["a", "b", "c"]
iex> Regex.split(~r/-/, "a-b-c", parts: 2)
["a", "b-c"]
iex> Regex.split(~r/-/, "abc")
["abc"]
iex> Regex.split(~r//, "abc")
["", "a", "b", "c", ""]
iex> Regex.split(~r/a(?<second>b)c/, "abc")
["", ""]
iex> Regex.split(~r/a(?<second>b)c/, "abc", on: [:second])
["a", "c"]
iex> Regex.split(~r/(x)/, "Elixir", include_captures: true)
["Eli", "x", "ir"]
iex> Regex.split(~r/a(?<second>b)c/, "abc", on: [:second], include_captures: true)
["a", "b", "c"]
iex> Regex.split(~r/-/, "-a-b--c", trim: true)
["a", "b", "c"]
to_embed(regex, embed_opts \ []) (1.19.0부터)
@spec to_embed(t(), [{:strict, boolean()}]) :: String.t()
패턴을 임베드할 수 있는 문자열로 반환합니다. 패턴이 현재 PCRE 버전에서 임베드 가능한 수정자로 표현될 수 없는 옵션으로 컴파일되었고 strict가 true(기본값)이면 ArgumentError 예외가 발생합니다. :strict 옵션이 false이면 문제가 되는 옵션이 없었던 것처럼 패턴이 반환되고 예외를 던지지 않습니다.
임베드 가능한 수정자/옵션은 현재:
- 'i' —
:caseless - 'm' —
:multiline - 's' —
:dotall, {:newline, :anycrlf} - 'x' —
:extended
임베드 불가능한 수정자는:
- 'f' —
:firstline - 'U' —
:ungreedy - 'u' —
:unicode, :ucp
여기 목록에 없는 다른 regex 컴파일 옵션은 임베드 불가능한 것으로 간주되어, :strict 옵션이 false가 아니면 예외를 던집니다.
iex> Regex.to_embed(~r/foo/)
"(?-imsx:foo)"
iex> Regex.to_embed(~r/^foo/m)
"(?m-isx:^foo)"
iex> Regex.to_embed(~r/foo # comment/ix)
"(?ix-ms:foo # comment\n)"
iex> Regex.to_embed(~r/foo/iu)
** (ArgumentError) regex compiled with options [:ucp, :unicode] which cannot be represented as an embedded pattern in this version of PCRE
iex> Regex.to_embed(~r/foo/imsxu, strict: false)
"(?imsx:foo\n)"
version() (1.4.0부터, deprecated)
이 함수는 deprecated입니다. :re.version()을 사용하세요. 기저 Regex 엔진의 버전을 반환합니다.
더 알아보기
~r시그널 — 정규 표현식 리터럴String— 문자열 함수와String.match?/2:re모듈 — Erlang 정규 표현식 모듈Regex.CompileError— 정규식 컴파일 실패 예외