
정규식은 매일 쓰는 게 아니라서, 한 달만 손을 놓아도 ?:가 뭐였는지, \b가 어디에 걸리는지 다시 찾아보게 됩니다. VB.NET에서 입력값 검사할 때, PHP에서 preg_match로 문자열 뽑을 때, 에디터에서 일괄 치환할 때 매번 같은 걸 검색하길래 한 장에 모아 두었습니다.
기본 메타문자
정규식에서 글자 그대로가 아니라 특별한 뜻으로 해석되는 기호를 메타문자라고 부릅니다. 가장 자주 쓰는 것들입니다.
| 표현 | 뜻 |
|---|---|
^x |
문자열이 x로 시작 |
x$ |
문자열이 x로 끝남 |
.x |
아무 문자 하나 뒤에 x가 옴 |
x+ |
x가 1번 이상 반복 |
x? |
x가 있어도 되고 없어도 됨 |
x* |
x가 0번 이상 반복 |
x\|y |
x 또는 y |
(x) |
x를 하나의 그룹으로 묶음 |
(x)(y) |
그룹이 여러 개면 앞에서부터 1, 2… 번호가 매겨지고 각각 따로 꺼내 쓸 수 있음 |
(x)(?:y) |
(?:)로 묶은 부분은 그룹으로 묶기만 하고 번호를 매기지 않음 |
x{n} |
x가 정확히 n번 |
x{n,} |
x가 n번 이상 |
x{n,m} |
x가 n번 이상 m번 이하 |
(?:)는 처음 보면 쓸데없어 보이는데, 반복이나 |를 묶어야 하지만 결과에서 꺼낼 필요는 없을 때 씁니다. 그룹 번호가 밀리지 않아서 나중에 $1, $2로 치환할 때 번호 계산이 편해집니다.
대괄호와 역슬래시 계열
[]는 안에 적은 글자들 중 딱 한 글자와 맞는다는 뜻입니다. 여기서 헷갈리는 게, 대괄호 안에 들어가면 메타문자 상당수가 그냥 글자로 바뀝니다. [.]는 아무 문자가 아니라 점 한 글자이고, [^...]의 ^는 시작이 아니라 "제외"가 됩니다.
| 표현 | 뜻 |
|---|---|
[xy] |
x 또는 y 중 한 글자 |
[^xy] |
x, y를 뺀 나머지 한 글자 |
[x-z] |
x부터 z까지 범위 중 한 글자 |
\^ |
메타문자 ^를 글자 그대로 사용 (escape) |
\b |
단어 경계 (단어 문자와 비단어 문자 사이의 위치) |
\B |
단어 경계가 아닌 위치 |
\d / \D |
숫자 / 숫자가 아닌 것 |
\s / \S |
공백 문자 / 공백이 아닌 것 |
\t |
탭 |
\v |
수직 탭 |
\w / \W |
영문자·숫자·밑줄(_) 중 하나 / 그 외 |
\b는 글자를 먹는 게 아니라 "위치"만 확인한다는 점을 기억해 두면 덜 헷갈립니다. 그리고 \w가 한글을 포함하는지는 엔진마다 다릅니다. .NET은 유니코드 문자로 보고 한글도 \w에 걸리지만, JavaScript는 기본적으로 영문·숫자·밑줄만 봅니다. 같은 패턴을 서버와 브라우저에서 같이 쓸 때 결과가 달라지는 흔한 원인입니다.
플래그 g, i, m
JavaScript처럼 /패턴/플래그 형태로 쓰는 곳에서는 뒤에 플래그를 붙입니다.
| 플래그 | 뜻 |
|---|---|
g |
전체 검색. 없으면 처음 맞는 것 하나만 찾고 멈춤 |
i |
대소문자 구분 안 함 |
m |
여러 줄 모드. ^, $가 문자열 전체가 아니라 각 줄의 시작과 끝에 맞음 |
VB.NET에서는 이런 플래그를 패턴 뒤에 붙이지 않고 RegexOptions.IgnoreCase, RegexOptions.Multiline으로 넘깁니다. 전체 검색은 Regex.Matches를 쓰면 됩니다.
간단한 패턴들
/[0-9]/g— 숫자 한 글자씩 전부 찾음/[to]/g— t 또는 o 글자를 전부 찾음 (단어 "to"가 아님)/filter/g— 글자 하나하나가 아니라 filter라는 연속된 문자열을 찾음
특정 단어만 빼고 나머지 단어를 고르고 싶을 때 두 가지 방식이 있습니다.
/\b(?:(?!to)\w)+\b/g
\b(?!\bto\b)\w+\b
첫 번째는 글자를 하나씩 먹을 때마다 "여기서 to가 시작되면 안 된다"를 검사하기 때문에, 단어 중간에 to가 들어 있는 "Tutorial" 같은 단어도 함께 빠집니다. 두 번째는 단어가 시작하는 위치에서 "이 단어 전체가 to인가"만 보므로 Tutorial은 정상적으로 잡힙니다. 정말로 to라는 단어 하나만 빼고 싶다면 두 번째가 의도에 맞습니다.
이메일 검사 패턴 풀어 보기
/^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i
앞에서부터 끊어 읽으면 이렇습니다. 영문자나 숫자 한 글자로 시작하고, 그 뒤로 "-, _, . 중 하나가 있을 수도 있고 없을 수도 있음 + 영문자나 숫자 한 글자" 묶음이 0번 이상 이어집니다. 그다음 @가 반드시 있고, 도메인 부분도 똑같은 규칙으로 이어진 뒤, 점 하나와 영문자 2~3글자로 끝납니다. i 플래그라 대소문자는 가리지 않습니다.
구분 기호가 연달아 두 개 나오거나(a..b) 구분 기호로 끝나는 아이디를 막아 준다는 점이 괜찮습니다. 다만 마지막 {2,3} 때문에 .info, .store처럼 4글자 이상인 최상위 도메인은 통과하지 못합니다. 실제 회원가입 폼에 쓴다면 {2,} 정도로 풀어 두는 걸 권합니다.
전화번호와 휴대폰 번호
/^\d{3}-\d{3,4}-\d{4}$/
숫자 3개, 하이픈, 숫자 3~4개, 하이픈, 숫자 4개로 끝나는 형식입니다. 하이픈이 반드시 있어야 하고, 02 같은 두 자리 지역번호는 걸러집니다. 서울 번호까지 받으려면 앞을 \d{2,3}으로 바꿔야 합니다.
/^01([0|1|6|7|8|9]?)-?([0-9]{3,4})-?([0-9]{4})$/
01로 시작하고, 그 뒤에 0·1·6·7·8·9 중 한 글자가 올 수도 있고, 하이픈은 있어도 되고 없어도 되며, 숫자 3~4개와 숫자 4개가 이어지는 형태입니다.
주의할 점이 하나 있습니다. 대괄호 안의 |는 "또는"이 아니라 그냥 글자입니다. 그래서 위 패턴은 01|-1234-5678 같은 이상한 값도 통과시킵니다. 의도대로 쓰려면 아래처럼 |를 빼는 게 맞습니다. 또 ? 때문에 세 번째 자리가 없어도 통과하니, 010 등을 꼭 받아야 한다면 ?도 빼세요.
/^01([016789])-?([0-9]{3,4})-?([0-9]{4})$/
URL 패턴
^(https?):\/\/([^:\/\s]+)(:([^\/]*))?((\/[^\s/\/]+)*)?\/?([^#\s\?]*)(\?([^#\s]*))?(#(\w*))?$
길어 보이지만 조각으로 나누면 읽을 만합니다.
^(https?):\/\/
([^:\/\s]+)
(:([^\/]*))?
((\/[^\s/\/]+)*)?\/?([^#\s\?]*)(\?([^#\s]*))?(#(\w*))?$
첫 줄은 http 또는 https와 ://, 둘째 줄은 콜론·슬래시·공백이 나오기 전까지의 호스트 이름입니다. 셋째 줄은 있을 수도 있는 :포트, 마지막 줄은 경로, 파일명, ? 뒤의 쿼리 문자열, # 뒤의 앵커를 차례로 그룹으로 잡습니다. 그룹마다 번호가 붙으니, 매치 결과에서 호스트나 쿼리 문자열만 골라 꺼내 쓰기 좋습니다.
VB.NET에서 써 보기
위 패턴들은 슬래시와 플래그를 떼고 문자열로 넣으면 .NET에서도 대부분 그대로 동작합니다.
Imports System.Text.RegularExpressions
Dim tel As String = "010-1234-5678"
If Regex.IsMatch(tel, "^01([016789])-?([0-9]{3,4})-?([0-9]{4})$") Then
Console.WriteLine("휴대폰 번호 형식입니다.")
End If
Dim mail As String = "Test.User@Example.com"
Dim ok As Boolean = Regex.IsMatch(mail,
"^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$",
RegexOptions.IgnoreCase)
폼 입력값 검사는 정규식 한 줄로 끝내고 싶어지지만, 이메일처럼 규칙이 복잡한 값은 "대충 형식이 맞는지"까지만 정규식으로 보고 실제 유효성은 인증 메일 같은 다른 수단으로 확인하는 편이 덜 골치 아픕니다.