정규표현식
📌시작하며
최근 프로그래머스 문제 풀이를 다시 시작했다. 알고리즘 강의를 듣고 있기도 하고, 꾸준히 해서 나쁠건 없으니 말이다.😊 문제를 풀다 보면, 특정 문제들에서는 정규표현식이 빛을 발하는데, 내가 JS 정규표현식을 완벽하게 숙지하고 있는 것이 아니라 난감할 때가 있다.
실무에서도 form을 만들 때 정규표현식을 이용해 값을 제한하기도 하는데, 이번 기회에 정규표현식을 정리해보고 숙지해보면 문제를 풀 때에도, 실무에서도 도움이 되리라 생각한다.
✅정규표현식
정규표현식(regular expression)은 문자열에서 특정 조합을 찾기 위한 패턴으로, JS에서 제공하는 메서드를 함께 사용할 수 있다.
☑정규표현식 생성하기
📌문자열로 작성
고정된 패턴의 정규식이라면, 아래와 같이 바로 작성할 수 있다.
1
const regex = /\w+/
📌RegExp 생성자
만약 동적으로 정규표현식을 생성해야 한다면, RegExp
생성자를 이용해 정규 표현식 객체를 생성할 수 있다. ⭐RegExp
메서드의 첫번째 인자는 정규표현식 패턴을, 두번째 인자는 플래를 전달하며, 둘 다 문자열 로 전달해야 함에 주의한다.
1
2
3
4
const regex1 = /\w+/
const regex2 = new RegExp("\\w+", "i")
console.log(regex1) // /\w+/
예를 들어, 사용자가 검색한 단어를 받아 데이터 목록에 그 단어가 있는지 찾아야 할 때를 생각해보자.
1
2
3
4
const searchKeyword = "hello" // 사용자가 입력한 단어
const regex = new RegExp(`${searchKeyword}`, "i") // i를 이용해 대소문자 구분 없이 검색함을 명시
console.log(regex.test("Hello World")) // true
console.log(regex.test("Hi")) // false
이런 상황에서는 정규 표현식을 동적으로 사용해야 할 필요성이 있고, 이 때 RegEXp
생성자를 이용하면 된다.
☑test
test()
메서드를 이용해 위에서 언급한 방법으로 만들어 둔 정규표현식을 문자열에 적용할 수 있다.
1
2
3
4
5
6
7
const regex1 = /ban*/i
const regex2 = new RegExp("ban*", "i")
const str = "I like banana"
console.log(regex1.test(str)) // true
console.log(regex2.test(str)) // true
☑match
정규표현식을 사용해 특정 문자열에서 원하는 문자의 매칭 결과를 확인할 수 있다. 특별한 플래그가 없다면, 첫 번째로 매칭된 결과만 반환한다.
먼저 한 자리 이상의 숫자(\d+) 뒤에 ‘세’가 붙어 있는 문자열을 찾는 정규표현식을 작성하고 적용해보자.
1
2
3
4
5
const regex = /\d+세/
const str =
"짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다."
const matchResult = str.match(regex)
console.log(matchResult) //["35세"]
만약. ‘세’라는 글자가 사용된 단어 여러개를 찾고 싶으면 g
플래그를 사용할 수 있다.
1
2
3
4
5
const regex = /\d+세/g // g 플래그 추가
const str =
"짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다."
const matchResult = str.match(regex)
console.log(matchResult) // ["35세", "29세", "5세", "1세"]
☑matchAll
모든 매칭 결과를 배열로 반환하는데, 세부정보를 포함한다. matchAll은 항상 g 플래그 를 사용해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const regex = /\d+세/g
const str =
"짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다."
const matchAllResult = [...str.matchAll(regex)]
console.log(matchAllResult)
// [
// [
// '35세',
// index: 13,
// input: '짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다.',
// groups: undefined
// ],
// [
// '29세',
// index: 23,
// input: '짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다.',
// groups: undefined
// ],
// [
// '5세',
// index: 33,
// input: '짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다.',
// groups: undefined
// ],
// [
// '1세',
// index: 42,
// input: '짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다.',
// groups: undefined
// ]
// ]
☑exec
exec 메서드는 정규식을 이용해 문자열에서 매칭된 결과를 세부 정보와 함께 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
const regex = /\d+세/
const str =
"짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다."
const execResult = regex.exec(str)
console.log(execResult)
// [
// '35세',
// index: 13, //매칭이 시작된 index 번호
// input: '짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다.',
// groups: undefined
// ]
‘g’ 플래그를 활용하면 n세 라는 단어들의 세부정보를 나타낸다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const regex = /\d+세/g
const str =
"짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다."
let execResult
while ((execResult = regex.exec(str)) !== null) {
console.log(execResult)
}
// [
// '35세',
// index: 13,
// input: '짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다.',
// groups: undefined
// ]
// [
// '29세',
// index: 23,
// input: '짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다.',
// groups: undefined
// ]
// [
// '5세',
// index: 33,
// input: '짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다.',
// groups: undefined
// ]
// [
// '1세',
// index: 42,
// input: '짱구 가족 중 신형만은 35세, 봉미선은 29세, 신짱구는 5세, 신짱아는 1세, 흰둥이는 8개월이다.',
// groups: undefined
// ]
✅플래그
위에서 언급한 플래그란 무엇일까? 만들어 둔 정규표현식의 일종의 옵션이라고 볼 수 있다.
☑ g (global)
주어진 문자열에서 해당 정규표현식에 해당하는 문자를 모두 찾는다. 아래의 예제에서는 ‘짱구’ 라는 단어 3가지를 찾았다. (신’짱구’도 인식한다!)
1
2
3
4
const regex = /짱구/g
const str = "짱구는 못말려의 주인공은 신짱구입니다. 짱구는 정말 못말려요."
const matchResult = str.match(regex)
console.log(matchResult) // ["짱구", "짱구", "짱구"]
☑ i (ignore case)
대소문자를 구별하지 않고 찾는다.
1
2
3
4
5
6
7
8
9
10
11
12
const regex = /i/gi //전부 찾고자 g 플래그도 추가해 주었다.
const text = "I like Lion"
console.log(text.match(regex)) //[ 'I', 'i', 'i' ]
const regex = /i/i //g 플래그를 삭제하면, 처음으로 일치하는 문자열만 보여준다.
const text = "I like Lion"
console.log(text.match(regex))
// [ 'I',
// index: 0,
// input: 'I like Lion',
// groups: undefined
// ]
☑ m (multiline)
여러 line에서 해당하는 문자열을 찾는다. m 플래그는 ^
와 $
가 동작하는 방식을 변경하는데, 전체 문자열의 시작과 끝을 나타내는 것이 아닌, ^
는 각 줄의 시작점을 나타내고, $
은 각 줄의 끝을 나타낸다.
1
2
3
4
5
6
7
8
const regex = /^신짱구/gm
//여러줄로 작성할 때 들여쓰기가 공백으로 인식되므로 주의한다.
const str = `신짱구 가족 중 신형만은 35세,
봉미선은 29세,
신짱구는 5세,
신짱아는 1세,
흰둥이는 8개월이다.`
console.log(str.match(regex)) //[ '신짱구', '신짱구' ]
✅그릅
[]
를 이용해 그룹을 지어 문자들 중 하나를 찾을 수 있다.
/신짱구/
‘신짱구’라는 하나의 단어를 찾는 것[신짱구]
는 “신”, “짱”, “구” 중 한 글자를 찾는 것
1
2
3
4
5
6
7
8
const regex1 = /신짱구/g // 문자열 "신짱구"를 전부 찾음
const regex2 = /[신짱구]/g // "신", "짱", "구" 중 하나의 문자를 전부 찾음
const str =
"신짱구는 신서준, 신지호, 신짱군, 신민구 에서 글자를 따와서 만든 이름이다."
console.log(str.match(regex1)) // ["신짱구"]
console.log(str.match(regex2)) // ['신', '짱', '구', '신', '신', '신', '짱', '신', '구']
이런 특징을 이용해, 아래와 같이 작성할 수 있다. | 표현식 | 의미 | |———-|——————————-| | [abc]
| 문자 a
, b
, c
중 하나 | | [A-Z]
| 대문자 A
부터 Z
중 하나 | | [a-z]
| 소문자 a
부터 z
중 하나 | | [A-z]
| 대문자 A
부터 소문자 z
중 하나 | | [가-하]
| 한글 가
부터 하
중 하나 |
✅Quantifiers (수량자)
Quantifiers(수량자)는 정규표현식에서 어떤 패턴이 몇 번 반복될지를 지정하는 데 사용
+
n+
는 하나 이상의 n을 포함하는 모든 문자열과 매칭된다.
1 2 3
let regex = /짱구+/g let text = "짱구야 놀자~ 내 이름은 신짱구 짱짱구 짱구짱구는 못말려" console.log(text.match(regex)) //[ '짱구', '짱구', '짱구', '짱구', '짱구' ]
1 2 3
let regex = /짱+/g let text = "짱구야 놀자~ 내 이름은 신짱구 짱짱구" console.log(text.match(regex)) //[ '짱', '짱', '짱짱' ]
위의 예제를 살펴보면
+
앞에 나와있는 글자를 반복 하는 내용을 찾아냄을 알 수 있다.짱구+
의 경우 짱구 를 포함하는 문자열과 매칭되므로, 짱구짱구 의 경우 “짱구”, “짱구” 두 가지를 따로 찾아내게 된다.*
- 앞에 오는 패턴 중 하나라도 일치하면 매칭된다.
1 2 3
let regex = /짱구*/g let text = "짱구야 놀자~ 내 이름은 신짱구 짱짱구 짱구짱구는 못말려" console.log(text.match(regex)) //[ '짱구', '짱구', '짱', '짱구', '짱구', '짱구' ]
위의
+
예제외는 다르게 짱짱구 에서 짱과 짱구를 다르게 검색했다.*
는 앞의 패턴이 없어도 허용되기 때문에 “짱” 단독으로도 매칭되기 떄문이다. 따라서+
보다 좀 더 광범위한 검색을 하게 되므로 사용에 주의해야 한다.?
- 바로 앞에 있는 패턴이 0번 또는 1번 등장하는 경우에 매칭된다.
- 즉 앞에 있는 패턴이 선택적 이란 의미가 된다.
1 2 3
let regex = /신짱?구/g let text = "신짱구의 이름은 짱구로, 짱을 빼면 신구가 된다." console.log(text.match(regex)) //[ '신짱구', '신구' ]
이 예제를 살펴보면, 신짱구 라는 패턴에서
짱?
을 통해 짱이라는 글자는 선택적임을 명시헀다. 따라서 짱이 있는신짱구
짱이 없는신구
를 검색하게 된다.{X}
- 정확히
X
번 반복하는 패턴을 찾는다.
1 2 3
let text = "100, 1000 or 10000?" let pattern = /\d{4}/g console.log(text.match(pattern)) // ["1000", "1000"]
d
는 숫자를 나타내므로, 숫자가 4번 반복되는 패턴을 찾는다.- 정확히
{X,Y}
- X번 이상 Y번 이하로 반복되는 패턴을 매칭한다.
1 2 3
let text = "100, 1000 or 10000?" let pattern = /\d{3,4}/g console.log(text.match(pattern)) //[ '100', '1000', '1000' ]
d
는 숫자를 나타내므로, 숫자가 3번 이상, 4번 이하로 반복되는 패턴을 찾는다.{X,}
- X번 이상 반복되는 패턴을 매칭한다.
1 2 3
let text = "1, 10, 100, 1000 or 10000?" let pattern = /\d{3,}/g console.log(text.match(pattern)) //[ '100', '1000', '10000' ]
$
- text 마지막에 특정 문자가 있는지 확인한다.
1 2 3 4
let regex = /is$/ let text = "Is this his" console.log(text.match(regex)) //[ 'is', index: 9, input: 'Is this his', groups: undefined ]
is로 문장이 종료되었기 때문에 자세한 정보가 출력된다.
1 2 3
let regex = /is$/ let text = "Is this hers" console.log(text.match(regex)) //null
is로 문장이 종료되지 않았기 때문에 null이 출력된다.
^
- text가 특정 문자로 시작하는지 확인한다.
1 2 3
let regex = /^Is/g let text = "Is this his" console.log(text.match(regex)) //["Is"];
1 2 3
let regex = /^Is/g let text = "Are there his books?" console.log(text.match(regex)) //null
?=
- 특정 패턴 이 뒤따라야 한다.
- 테스트를 수행한 뒤의 뒤따라야 하는 특정 패턴 은 매칭 결과에 포함되지 않는다.
- 해당 문자가 정확히 일치해야 하므로, 이어지는 특정 패턴은 띄어쓰기 를 해서 찾으려고 하는 문자와 다음 패턴을 분리해야 한다.
1 2 3
let regex = /짱구(?= 엄마)/g let text = "짱구 엄마 이름은 봉미선, 짱구 아빠 이름은 신형만" console.log(text.match(regex)) //['짱구']
1 2 3
let regex = /짱구(?= 엄마)/g let text = "짱구엄마 이름은 봉미선, 짱구아빠 이름은 신형만" console.log(text.match(regex)) //null
?!
- 특정 패턴 이 뒤따르지 않는지 검사한다.
1 2 3
let regex = /짱구(?! 엄마)/g let text = "짱구 엄마 이름은 봉미선" console.log(text.match(regex)) //null
1 2 3
let regex = /짱구(?! 엄마)/g let text = "짱구 아빠 이름은 신형만" console.log(text.match(regex)) //['짱구']
이 예제에서
짱구
다음엄마
가 등장하지 않기 떄문에짱구
를 찾아낸다.
✅Metacharacter (메타문자)
✅Metacharacter(메타문자)는 정규 표현식에서 검색 조건, 텍스트 조작 방식을 정의하는 특별한 문자다.
.
.
은 임의의 한 문자를 의미한다.- 예를 들어,
A.B
로 작성할 때는, A와 B사이에 어떤 한 문자가 들어간 문자열을 찾는다. .
도 하나의 문자를 나타내므로 이때AB
는 검색되지 않음에 주의한다.
1 2 3
let regex = /신.구/g let text = "신짱구 신형구 신진구 신짱아 김짱구 제갈신짱구" console.log(text.match(regex)) //[ '신짱구', '신형구', '신진구', '신짱구' ]
1 2 3
let regex = /신짱.구/g let text = "신짱일구 신짱이구 신짱삼구 신짱구 김짱구 제갈신짱구" console.log(text.match(regex)) //[ '신짱일구', '신짱이구', '신짱삼구' ]
1 2 3
let regex = /신짱..구/g let text = "신짱일이구 신짱이삼구 신짱사구 신짱구 김짱구 제갈신짱구" console.log(text.match(regex)) //[ '신짱일이구', '신짱이삼구' ]
신짱
과구
사이에 임의의 두 문자 가 들어가야 함을 명시했으므로,신짱사구
,신짱구
는 매칭되지 않는다.\w
- word character(단어 문자)를 의미한다.
- 알파벳 대소문자 ([a-z], [A-Z])
- 숫자 ([0-9])
- 언더스코어 (_)
1 2 3
let regex = /\w/g let text = "올해 5살인 짱구의 ID는 shin_5@ 이다." console.log(text.match(regex)) //["5", "I", "D", "s", "h", "i", "n", "_", "5"]
한글은 단어문자에 포함되지 않으므로 매칭되지 않으며, 알파벳 대소문자, 숫자, 언더스코어가 한글자씩 매칭된다.
- word character(단어 문자)를 의미한다.
\W
- 단어 문자가 아닌 것과 매칭된다.
- 공백도 단어 문자가 아니므로 매칭된다.
1 2 3 4
let regex = /\W/g let text = "올해 5살인 짱구의 ID는 shin_5@ 이다." console.log(text.match(regex)) //["올", "해", " ", "살", "인", " ", "짱", "구", "의", " ", "는", " ", "@", " ", "이", "다", "."];
-\b
단어 경계를 나타낸다.
- 단어 경계란, 단어 문자와 비단어 문자가 만나는 지점을 의미한다.
-\B
- 비단어 경계를 나타낸다.
- 비단어 경계란, 단어 문자와 단어 문자가 이어지거나, 비단어 문자와 비단어 문자가 이어지는 지점을 의미한다.
➡️ 단어 문자와 비단어 문자 비교
구분 | 문자 | 설명 |
---|---|---|
단어 문자 | a-z , A-Z | 알파벳 소문자와 대문자 |
0-9 | 숫자 | |
_ | 언더스코어 | |
비-단어 문자 | 공백 ( , \t , \n , 등) | 공백 문자 (스페이스, 탭, 줄바꿈 등) |
특수문자 (! , @ , # , $ , % , 등) | 알파벳, 숫자, 언더스코어가 아닌 모든 특수문자 | |
구두점 (. , , , ? , : , ; , 등) | 구두점 기호 | |
제어문자 (\0 , \r , 등) | NULL 문자, *캐리지 리턴 등 | |
문자 이외의 모든 것 | 문자로 간주되지 않는 모든 비문자 |
- *캐리지 리턴: 과거 타자기에서 유래한 개념으로 커서를 현재 줄의 시작으로 이동시키는 제어 문자를 말한다.
➡️ 예제
사실 위에 설명만 보면 무슨말인지 이해가 안가는데, 아래 예제를 살펴보자.
1
hello world! 123step _my_favorite
이제 여기서 단어 경계를 찾아보면, 아래와 같이 8개의 따옴표로 묶인 빈 문자가 8개가 출력된다.
1
2
3
4
5
6
7
const text = "hello world! 123step _my_favorite"
console.log(text.match(/\b/g)) // 단어 경계 위치를 확인
// [
// '', '', '', '',
// '', '', '', ''
// ]
이걸 시각적으로 나타내면 다음과 같다. |
가 8개 있는 걸 확인할 수 있다.
1
|hello| |world|! |123step| |_my_favorite|
이떄 주의할점은 world!
에서 !
는 비단어 문자이므로, world!
가 아니라 world
와 !(공백)
으로 나뉘게 된다는 것이다.
➡️ 프로그래머스 문제 응용
사실 이 부분 이해가 어려워서 일단 넘어가려고 했는데,프로그래머스에 응용하기 좋은 문제가 있어서 설명을 덧붙인다.
JadenCase를 만드는 문제인데, 이 문제의 핵심은 각 단어의 첫 문자에 toUpperCase
를 적용하고, 다른 문자에는 toLowerCase()
를 적용해주는 것이다.
1
2
3
4
5
6
7
//입력값: "3people unFollowed me"
//출력값: "3people Unfollowed Me"
function solution(s) {
return s
.replace(/\b\w/g, (match) => match.toUpperCase())
.replace(/\B\w+/g, (match) => match.toLowerCase())
}
\d
- 숫자와 매칭된다.
1 2 3
let regex = /\d/g let text = "짱구는 5살, 짱아는 1살로 4살 터울의 남매다." console.log(text.match(regex)) //[ '5', '1', '4' ]
\D
- 숫자가 아닌 문자와 매칭된다.
1 2 3
let regex = /\D/g let text = "짱구는 5살" console.log(text.match(regex)) //[ '짱', '구', '는', ' ', '살' ]
\s
- 공백과 매칭된다.
1 2 3
let regex = /\s/g let text = "짱구는 5살" console.log(text.match(regex)) //[ ' ' ]
\S
- 공백이 아닌 것과 매칭된다.
1 2 3
let regex = /\S/g let text = "짱구는 5살" console.log(text.match(regex)) //[ '짱', '구', '는', '5', '살' ]
\0
- null 문자가 있는지 찾는다.
1 2 3 4 5 6 7 8 9
let regex = /\0/ let text = "짱구야 놀자~ \0 떡잎마을방범대 출동!" console.log(text.match(regex)) //[ // '\x00', // index: 8, // input: '짱구야 놀자~ \x00 떡잎마을방범대 출동!', // groups: undefined // ]
null문자가 없는 경우에는 null을 출력한다.
1 2 3
let regex = /\0/ let text = "짱구야 놀자~ 떡잎마을방범대 출동!" console.log(text.match(regex)) //null
\n
- 줄바꿈 문자가 포함되었는지 찾는다.
1 2 3 4
let regex = /\n/ let text = "짱구야 놀자~ /\n 떡잎마을방범대 출동!" console.log(text.match(regex)) // [ '\n', index: 9, input: '짱구야 놀자~ /\n 떡잎마을방범대 출동!', groups: undefined ]
줄바꿈 문자가 없는 경우에는 null을 출력한다.
1 2 3
let regex = /\n/ let text = "짱구야 놀자~ 떡잎마을방범대 출동!" console.log(text.match(regex)) //null