개발하는 일상

js 정규표현식으로 cli와 비슷하게 문자열 구분하기(javascript split string by space, but ignore space in quotes) 본문

개발 간단 팁

js 정규표현식으로 cli와 비슷하게 문자열 구분하기(javascript split string by space, but ignore space in quotes)

롯데빙빙바 2021. 4. 28. 12:40

stackoverflow.com/questions/16261635/javascript-split-string-by-space-but-ignore-space-in-quotes-notice-not-to-spli

이 글은 위 질문의 답변을 해석한 내용입니다.

먼저 코드

const command = "git commit -m 'hello world'"
const splitedCommand = command.match(/(?:[^\s']+|'[^']*')+/g)
console.log(splitedCommand)
// ['git', 'commit', '-m', "'hello world'"]

CLI스럽게 문자열 나누기

cli에서 명령어를 입력할 때, 공백(space)를 기준으로 명령어를 나눠 인식하지만, 따옴표(quote)안에 있는 공백(space)는 나누어 인식하지 않습니다. 아래 명령어를 보세요.

$ git commit -m 'hello world'

여기서 명령어는 git, commit, -m, 'hello world'로 나뉘어 처리됩니다. js의 regexp로 이 과정을 만들어보겠습니다.

정규식 해설

괄호안의 ?:는 일단 무시하겠습니다.

/([^\s']+|'[^']*')+/

위 정규식을 먼저 크게 생각해보면, 제일 바깥은 ()+ 구조입니다. 괄호안의 패턴이 1회 이상 반복되는 패턴을 말합니다.

괄호안에는 x|y의 구조이고, x 혹은 y에 해당하는 패턴입니다.

| 왼쪽은 [^\s']+입니다.
[]는 문자셋으로, 문자범위 등을 지정하는 것이 가능합니다.
문자셋 안에 처음 쓰인 ^는 부정의 의미입니다. 뒤에 붙는 문자열이 아닌 문자열을 의미합니다.
문자셋안에서 \s는 공백을, '는 그냥 작은 따옴표를 의미합니다.
마지막의 +는 1회 이상이라고 말했었죠? 정리하면 공백과 작은따옴표가 아닌 문자열이 1회 이상 반복되는 문자열입니다.
위의 예시에서는 git, commit 등이 해당되겠죠?

| 오른쪽은 '[^']*'입니다.
여기서 처음 보는 건 *인데, *앞의 패턴이 0회 이상 반복되는 패턴을 의미합니다.
정리하면, '로 시작되고, 중간에는 따옴표가 아닌 문자가 0회 이상 반복되며, 마지막 문자는 '가 되는 문자열입니다.
위의 예시에서는 'hello world'가 해당됩니다.

마지막으로 정리하면, 공백이나 따옴표가 들어가지 않은 문자열 혹은, 따옴표로 감싸진 문자열이 1회 이상 반복되는 패턴이 됩니다.

match와 global

js에서 match는 인자로 주어진 정규표현식에 맞는 문자열을 찾아 반환합니다. 아래의 코드를 보세요.

const foo = 'bar foo zoo'
console.log(foo.match(/.oo/))
// ["foo", index: 4, input: "bar foo zoo", groups: undefined]

정규식에 있는 .은 개행문자를 제외한 모든 문자에 대응됩니다. 그래서 해당 정규식은 oo로 끝나는 어떤 문자열 정도로 생각하시면 되겠습니다.
global플래그가 없으면 match는 해당 정규식에 맞는 처음 문자를 찾아 그 정보를 리턴합니다. 이번에는 global을 달아보겠습니다.

const foo = 'bar foo zoo'
console.log(foo.match(/.oo/g))
// ["foo", "zoo"]

이번에는 조건에 맞는 모든 문자열이 리턴됩니다. 제가 하려고 하는 일에는 global플래그가 필요하네요.

non-capturing group

아까 건너뛰었던 ?:에 대한 내용입니다.
정규표현식에서 괄호를 사용하면 그룹으로 간주되어 match에서 전체 정규식에 대한 결과 말고도 그룹에 대한 결과도 리턴해줍니다.
아래의 예시를 보세요.

const tag = '<div>hello</div>'
console.log(tag.match(/<(.*?)>/))
// ["<div>", "div", index: 0, input: "<div>hello</div>", groups: undefined]

match의 결과로 <div>말고도 div도 출력된 것을 보세요. 전체 정규식에 해당하는 문자열 말고도 괄호 안의 정규식에 해당하는 문자열도 결과에 같이 들어있습니다. 이 것을 캡쳐링이라고 하는데, 만약 내가 괄호를 정규식을 묶는데만 활용하고 싶고 캡쳐링하고 싶진 않다면, 그 때 여는 괄호 뒤에 ?:를 써줘서 캡쳐링하지 않을 수 있습니다.
아래 예시에서 div가 사라진 걸 확인하세요.

const tag2 = '<div>hello</div>'
console.log(tag2.match(/<(?:.*?)>/))
// ["<div>", index: 0, input: "<div>hello</div>", groups: undefined]

우리의 코드에서는 괄호를 캡쳐링하려고 사용한 것이 아니라 +를 적절히 사용하기 위해서 사용한 것이라 ?:가 들어 있습니다. 다만, match와 global 플래그를 같이 사용한다면 이 캡쳐링기능이 의미가 없어지기 때문에, 이렇게만 사용한다면 ?:를 쓰든, 쓰지 않든 다른 결과를 불러오지는 않습니다.

Comments