작은 따옴표 나 큰 따옴표로 묶지 않을 때 공백을 사용하여 문자열을 분할하는 정규식


114

정규식을 처음 사용하며 도움을 주시면 감사하겠습니다. 작은 따옴표 나 큰 따옴표로 묶지 않은 모든 공백을 사용하여 예제 문자열을 분할하는 표현식을 작성하려고합니다. 내 마지막 시도는 다음과 같습니다. (?!")그리고 제대로 작동하지 않습니다. 따옴표 앞의 공간에서 분할됩니다.

입력 예 :

This is a string that "will be" highlighted when your 'regular expression' matches something.

원하는 출력 :

This
is
a
string
that
will be
highlighted
when
your
regular expression
matches
something.

참고 "will be"'regular expression'단어 사이의 간격을 유지한다.


실제로 "split"메서드를 사용하고 있습니까? 아니면 Matcher에서 "find"메서드를 사용하여 루핑하면 충분합니까?
erickson

9
"그리고 지금은 두 가지 문제가있다"

답변:


251

나는 다른 모든 사람들이 왜 그렇게 복잡한 정규식이나 그렇게 긴 코드를 제안하는지 이해하지 못합니다. 기본적으로 문자열에서 두 가지 종류의 항목을 가져 오려고합니다. 즉, 공백이나 따옴표가 아닌 문자 시퀀스와 두 종류의 따옴표에 대해 따옴표로 시작하고 끝나는 문자 시퀀스 (사이에 따옴표없이)가 있습니다. 다음 정규식을 사용하여 이러한 항목을 쉽게 일치시킬 수 있습니다.

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

목록에서 따옴표를 원하지 않기 때문에 캡처 그룹을 추가했습니다.

이 Java 코드는 목록을 작성하고 일치하는 경우 캡처 그룹을 추가하여 따옴표를 제외하고 캡처하는 그룹이 일치하지 않으면 전체 정규식 일치를 추가합니다 (인용되지 않은 단어가 일치 함).

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    if (regexMatcher.group(1) != null) {
        // Add double-quoted string without the quotes
        matchList.add(regexMatcher.group(1));
    } else if (regexMatcher.group(2) != null) {
        // Add single-quoted string without the quotes
        matchList.add(regexMatcher.group(2));
    } else {
        // Add unquoted word
        matchList.add(regexMatcher.group());
    }
} 

반환 된 목록에 따옴표가 있어도 괜찮다면 훨씬 더 간단한 코드를 사용할 수 있습니다.

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"[^\"]*\"|'[^']*'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    matchList.add(regexMatcher.group());
} 

1
Jan, 응답 해 주셔서 감사합니다. BTW, 저는 EditPad의 열렬한 팬입니다.
carlsz

문자열에 이스케이프 된 따옴표를 허용하려면 어떻게해야 \"합니까?
Monstieur 2014 년

3
이 답변의 문제는 타의 추종을 불허하는 인용문입니다. John's mother결과가 [John, s, mother]
분할

2
leonbloy 개요 문제를 해결하려면 피연산자를 약간 재정렬하고 whitespace-group :에서 따옴표를 생략 할 수 있습니다 "([^"]*)"|'([^']*)'|[^\s]+.
Ghostkeeper

1
이 답변 및 기타 답변을 기반으로 다음 정규식은 따옴표 안의 문자를 이스케이프하도록 허용합니다 "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+.. stackoverflow.com/questions/5695240/
Limnic

15

정규식을 사용하여 다양한 컨텍스트에서 동일한 질문을 다루는 StackOverflow에 대한 몇 가지 질문이 있습니다. 예를 들면 :

UPDATE : 작은 따옴표와 큰 따옴표로 묶인 문자열을 처리하기위한 샘플 정규식입니다. 참고 : 따옴표 안에있을 때를 제외하고 어떻게 문자열을 분할 할 수 있습니까?

m/('.*?'|".*?"|\S+)/g 

빠른 Perl 스 니펫으로 이것을 테스트했으며 출력은 아래와 같이 재현되었습니다. 또한 따옴표 사이에있는 경우 빈 문자열 또는 공백 전용 문자열에 대해 작동합니다 (원하는지 여부는 확실하지 않음).

This
is
a
string
that
"will be"
highlighted
when
your
'regular expression'
matches
something.

여기에는 일치하는 값에 따옴표 문자 자체가 포함되지만 문자열 바꾸기로 제거하거나 정규식을 수정하여 포함하지 않을 수 있습니다. 나는 2am이 더 이상 정규 표현식을 엉망으로 만들기에는 너무 늦기 때문에 독자 또는 다른 포스터를위한 연습으로 남겨 둘 것입니다.)


귀하의 정규식은 "will be"및 "regular expression"과 같이 일치하지 않는 따옴표를 허용한다고 생각합니다.
Zach Scrivena

@Zach-당신 말이 맞아요 ... 만약을 대비해서 그것을 고치도록 업데이트했습니다
Jay


3

Jan Goyvaerts의 정규식은 지금까지 찾은 최고의 솔루션이지만 빈 (null) 일치 항목도 생성하여 프로그램에서 제외합니다. 이러한 빈 일치는 정규식 테스터 (예 : rubular.com)에서도 나타납니다. 검색 배열을 바꾸면 (먼저 인용 된 부분과 공백으로 구분 된 단어보다 검색) 다음을 사용하여 한 번에 수행 할 수 있습니다.

("[^"]*"|'[^']*'|[\S]+)+

2
(?<!\G".{0,99999})\s|(?<=\G".{0,99999}")\s

이것은 큰 따옴표로 묶지 않은 공백과 일치합니다. Java는 lookbehind에서 * 및 +를 지원하지 않기 때문에 min, max {0,99999}를 사용해야합니다.


1

문자열을 검색하고 각 부분을 잡아서 분할하는 것이 더 쉬울 것입니다.

이유는 이전과 이후의 공간에서 분할 할 수 있기 때문 "will be"입니다. 그러나 분할 내부 사이의 공간을 무시하는 것을 지정하는 방법을 생각할 수 없습니다.

(실제 Java가 아님)

string = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";

regex = "\"(\\\"|(?!\\\").)+\"|[^ ]+"; // search for a quoted or non-spaced group
final = new Array();

while (string.length > 0) {
    string = string.trim();
    if (Regex(regex).test(string)) {
        final.push(Regex(regex).match(string)[0]);
        string = string.replace(regex, ""); // progress to next "word"
    }
}

또한 작은 따옴표를 캡처하면 다음과 같은 문제가 발생할 수 있습니다.

"Foo's Bar 'n Grill"

//=>

"Foo"
"s Bar "
"n"
"Grill"

솔루션은 Carl의 예의 일부인 작은 따옴표로 묶인 문자열을 처리하지 않습니다.
Jan Goyvaerts

1

String.split()따옴표 안의 공백 (분할하지 않음)과 바깥 쪽 (분할)을 구분할 방법이 없기 때문에 여기서는 도움이되지 않습니다. Matcher.lookingAt()아마도 필요한 것입니다.

String str = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
str = str + " "; // add trailing space
int len = str.length();
Matcher m = Pattern.compile("((\"[^\"]+?\")|('[^']+?')|([^\\s]+?))\\s++").matcher(str);

for (int i = 0; i < len; i++)
{
    m.region(i, len);

    if (m.lookingAt())
    {
        String s = m.group(1);

        if ((s.startsWith("\"") && s.endsWith("\"")) ||
            (s.startsWith("'") && s.endsWith("'")))
        {
            s = s.substring(1, s.length() - 1);
        }

        System.out.println(i + ": \"" + s + "\"");
        i += (m.group(0).length() - 1);
    }
}

다음 출력을 생성합니다.

0: "This"
5: "is"
8: "a"
10: "string"
17: "that"
22: "will be"
32: "highlighted"
44: "when"
49: "your"
54: "regular expression"
75: "matches"
83: "something."

1

Marcus의 접근 방식이 마음에 들었지만 따옴표 근처에 텍스트를 허용하고 "및 '따옴표 문자를 모두 지원할 수 있도록 수정했습니다. 예를 들어 [a =,"로 분할하지 않으려면 a = "some value"가 필요했습니다. 일부 가치 "].

(?<!\\G\\S{0,99999}[\"'].{0,99999})\\s|(?<=\\G\\S{0,99999}\".{0,99999}\"\\S{0,99999})\\s|(?<=\\G\\S{0,99999}'.{0,99999}'\\S{0,99999})\\s"

1

Jan의 접근 방식은 훌륭하지만 여기에 기록을위한 또 다른 접근 방식이 있습니다.

실제로 제목에서 언급 한대로 분할하고 따옴표를 "will be"및에 유지하려면 s1, s2, s3 등의 상황을 제외하고 패턴'regular expression'일치 (또는 대체)하는 이 방법을 사용할 수 있습니다.

정규식 :

'[^']*'|\"[^\"]*\"|( )

두 개의 왼쪽 교대가 complete 'quoted strings'"double-quoted strings". 이러한 일치를 무시합니다. 오른쪽은 그룹 1과 일치하고 공백을 캡처하며 왼쪽의 표현식과 일치하지 않았기 때문에 오른쪽 공백이라는 것을 알고 있습니다. 우리는 그것들을 SplitHere다음으로 분할합니다 SplitHere. 다시 말하지만, 이것은 당신이 원하는 진정한 분할 케이스를위한 "will be"것이지 will be.

다음은 완전히 작동하는 구현입니다 ( 온라인 데모 에서 결과 참조 ).

import java.util.*;
import java.io.*;
import java.util.regex.*;
import java.util.List;

class Program {
public static void main (String[] args) throws java.lang.Exception  {

String subject = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
Pattern regex = Pattern.compile("\'[^']*'|\"[^\"]*\"|( )");
Matcher m = regex.matcher(subject);
StringBuffer b= new StringBuffer();
while (m.find()) {
    if(m.group(1) != null) m.appendReplacement(b, "SplitHere");
    else m.appendReplacement(b, m.group(0));
}
m.appendTail(b);
String replaced = b.toString();
String[] splits = replaced.split("SplitHere");
for (String split : splits) System.out.println(split);
} // end main
} // end Program

1

C #을 사용하는 경우 다음을 사용할 수 있습니다.

string input= "This is a string that \"will be\" highlighted when your 'regular expression' matches <something random>";

List<string> list1 = 
                Regex.Matches(input, @"(?<match>\w+)|\""(?<match>[\w\s]*)""|'(?<match>[\w\s]*)'|<(?<match>[\w\s]*)>").Cast<Match>().Select(m => m.Groups["match"].Value).ToList();

foreach(var v in list1)
   Console.WriteLine(v);

특별히 " | <(? [\ w \ s] *)> "를 추가하여 모든 문자를 그룹 구문에 지정할 수 있음을 강조했습니다. (이 경우 <> 를 사용 하여 그룹화합니다.

출력은 다음과 같습니다.

This
is
a
string
that
will be
highlighted
when
your
regular expression 
matches
something random

0

정규 표현식만으로는 이것이 불가능하다고 합리적으로 확신합니다. 다른 태그 안에 무언가가 포함되어 있는지 확인하는 것은 파싱 작업입니다. 이것은 정규식으로 XML을 구문 분석하려는 것과 동일한 문제처럼 보입니다. 올바르게 수행 할 수 없습니다. 인용 된 문자열과 일치하는 탐욕스럽지 않은 비전 역 정규식을 반복적으로 적용하여 원하는 결과를 얻을 수 있습니다. 그런 다음 다른 것을 찾을 수 없으면 공백으로 분할합니다. 모든 하위 문자열의 원래 순서를 추적하는 것을 포함한 문제. 가장 좋은 방법은 문자열을 반복하고 원하는 토큰을 꺼내는 정말 간단한 함수를 작성하는 것입니다.


정규식으로 가능합니다. 링크 한 일부 샘플을 참조하십시오. 이것에 대한 몇 가지 변형이 있으며 정규 표현식을 통해이를 해결하는 몇 가지 유사한 질문을 보았습니다.
제이

1
정규식을 사용하지 않을 때를 아는 것이 (? : ([ ' "]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
Rene

0

Jan의 수락 된 답변에 대한 몇 가지 도움이되는 조정 :

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • 따옴표로 묶인 문자열 내에서 이스케이프 된 따옴표를 허용합니다.
  • 작은 따옴표와 큰 따옴표에 대한 패턴 반복을 피합니다. 이것은 또한 필요한 경우 더 많은 인용 기호를 추가하는 것을 단순화합니다 (캡처 그룹이 하나 더 필요함).

이것은 아포스트로피가 포함 된 단어를 분리합니다. 예를 들면 다음과 같습니다.you're
Design by Adrian

0

다음을 시도해 볼 수도 있습니다.

    String str = "This is a string that \"will be\" highlighted when your 'regular expression' matches something";
    String ss[] = str.split("\"|\'");
    for (int i = 0; i < ss.length; i++) {
        if ((i % 2) == 0) {//even
            String[] part1 = ss[i].split(" ");
            for (String pp1 : part1) {
                System.out.println("" + pp1);
            }
        } else {//odd
            System.out.println("" + ss[i]);
        }
    }

이것이 작동하는 이유에 대한 설명을 추가해야합니다. 코드 자체에 코드와 주석을 추가 할 수도 있습니다. 현재 형식에서는 나머지 커뮤니티가 무엇을 이해하는 데 도움이 될 수있는 설명을 제공하지 않습니다. 질문을 해결 / 답변했습니다. 이것은 이미 답변이있는 질문에 특히 중요합니다.
ishmaelMakitla

0

다음은 인수 배열을 반환합니다. 인수는 작은 따옴표 나 큰 따옴표에 포함되지 않는 한 공백으로 분할 된 'command'변수입니다. 그런 다음 일치 항목을 수정하여 작은 따옴표와 큰 따옴표를 제거합니다.

using System.Text.RegularExpressions;

var args = Regex.Matches(command, "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'").Cast<Match>
().Select(iMatch => iMatch.Value.Replace("\"", "").Replace("'", "")).ToArray();

2
다른 사람들이 더 쉽게 이해할 수 있도록 답변에 약간의 설명을 추가 할 수 있습니까? 이상적으로 우리는 코드 전용 답변을 피하고 싶습니다.
Jaquez 19

0

String.split ()을 사용하는 첫 번째 한 줄

String s = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
String[] split = s.split( "(?<!(\"|').{0,255}) | (?!.*\\1.*)" );

[This, is, a, string, that, "will be", highlighted, when, your, 'regular expression', matches, something.]

공백이 작은 따옴표 또는 큰 따옴표로 둘러싸여있는 경우 공백에서 분할하지 마십시오
. 왼쪽의 255 자 및 공백의 오른쪽에있는 모든 문자가 작은 따옴표도 아니고 큰 따옴표도 아닌 경우 공백에서 분할됩니다.

원본 게시물 에서 수정 됨 (큰 따옴표 만 처리)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.