Post

정규표현식

📌시작하며

최근 프로그래머스 문제 풀이를 다시 시작했다. 알고리즘 강의를 듣고 있기도 하고, 꾸준히 해서 나쁠건 없으니 말이다.😊 문제를 풀다 보면, 특정 문제들에서는 정규표현식이 빛을 발하는데, 내가 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"]
    

    한글은 단어문자에 포함되지 않으므로 매칭되지 않으며, 알파벳 대소문자, 숫자, 언더스코어가 한글자씩 매칭된다.

  • \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
    

🗂️참고 사이트

This post is licensed under CC BY 4.0 by the author.