Regex để tách một chuỗi bằng cách sử dụng dấu cách khi không được bao quanh bởi dấu ngoặc kép hoặc đơn


114

Tôi mới sử dụng cụm từ thông dụng và rất mong sự giúp đỡ của bạn. Tôi đang cố gắng tập hợp một biểu thức sẽ chia chuỗi ví dụ bằng cách sử dụng tất cả các khoảng trắng không được bao quanh bởi dấu ngoặc kép hoặc đơn. Nỗ lực cuối cùng của tôi trông như thế này: (?!")và không hiệu quả. Nó tách ra trên không gian trước khi trích dẫn.

Ví dụ đầu vào:

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

Sản phẩm chất lượng:

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

Lưu ý điều đó "will be"'regular expression'giữ lại khoảng cách giữa các từ.


Bạn thực sự đang sử dụng phương thức "split" hay lặp lại với phương thức "find" trên Matcher là đủ?
erickson 14/12/08

9
"và bây giờ ông có hai vấn đề"

Câu trả lời:


251

Tôi không hiểu tại sao tất cả những người khác lại đề xuất các biểu thức chính quy phức tạp hoặc mã dài như vậy. Về cơ bản, bạn muốn lấy hai loại thứ từ chuỗi của mình: chuỗi ký tự không phải là dấu cách hoặc dấu ngoặc kép và chuỗi ký tự bắt đầu và kết thúc bằng dấu ngoặc kép, không có dấu ngoặc kép ở giữa, đối với hai loại dấu ngoặc kép. Bạn có thể dễ dàng đối sánh những thứ đó với biểu thức chính quy này:

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

Tôi đã thêm các nhóm chụp vì bạn không muốn các trích dẫn trong danh sách.

Mã Java này xây dựng danh sách, thêm nhóm thu thập nếu nó phù hợp để loại trừ các dấu ngoặc kép và thêm đối sánh regex tổng thể nếu nhóm thu thập không khớp (một từ chưa được trích dẫn đã khớp).

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());
    }
} 

Nếu bạn không phiền khi có các dấu ngoặc kép trong danh sách trả về, bạn có thể sử dụng mã đơn giản hơn nhiều:

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, cảm ơn vì phản hồi của bạn. BTW, tôi là một fan hâm mộ lớn của EditPad.
carlsz 14/12/08

Điều gì sẽ xảy ra nếu tôi muốn cho phép dấu ngoặc kép trong chuỗi \"?
Monstieur

3
Vấn đề với câu trả lời này là với câu trích dẫn chưa từng có: John's motherkết quả được phân tách trong[John, s, mother]
leonbloy

2
Để khắc phục các vấn đề cương leonbloy, bạn có thể sắp xếp lại các toán hạng một chút và bỏ qua các trích dẫn từ các khoảng trắng nhóm: "([^"]*)"|'([^']*)'|[^\s]+.
Ghostkeeper

1
Xây dựng dựa trên này và câu trả lời khác, regex sau cho phép thoát nhân vật bên trong dấu ngoặc kép: "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+. Xem stackoverflow.com/questions/5695240/…
Limnic

15

Có một số câu hỏi trên StackOverflow bao gồm cùng một câu hỏi này trong các ngữ cảnh khác nhau bằng cách sử dụng cụm từ thông dụng. Ví dụ:

CẬP NHẬT : Mẫu regex để xử lý các chuỗi được trích dẫn đơn và kép. Tham khảo: Làm cách nào tôi có thể tách trên một chuỗi ngoại trừ khi bên trong dấu ngoặc kép?

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

Đã kiểm tra điều này bằng một đoạn mã Perl nhanh và kết quả như được tái tạo bên dưới. Cũng hoạt động đối với các chuỗi trống hoặc chuỗi chỉ có khoảng trắng nếu chúng nằm giữa các dấu ngoặc kép (không chắc liệu đó có phải là mong muốn hay không).

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

Lưu ý rằng điều này không bao gồm chính các ký tự trích dẫn trong các giá trị phù hợp, mặc dù bạn có thể loại bỏ điều đó bằng một chuỗi thay thế hoặc sửa đổi regex để không bao gồm chúng. Tôi sẽ để nó như một bài tập cho người đọc hoặc một người đăng khác vào lúc này, vì 2 giờ sáng là quá muộn để làm rối với các cụm từ thông dụng nữa;)


Tôi nghĩ rằng regex của bạn cho phép các dấu ngoặc kép không khớp, ví dụ: "sẽ là" và "cụm từ thông dụng".
Zach Scrivena 14/12/08

@Zach - bạn nói đúng, nó đúng ... đã cập nhật nó để khắc phục điều đó đề phòng
Jay

6

Nếu bạn muốn cho phép dấu ngoặc kép bên trong chuỗi, bạn có thể sử dụng một cái gì đó như sau:

(?:(['"])(.*?)(?<!\\)(?>\\\\)*\1|([^\s]+))

Chuỗi được trích dẫn sẽ là nhóm 2, các từ đơn lẻ không được trích dẫn sẽ là nhóm 3.

Bạn có thể thử nó trên nhiều chuỗi khác nhau tại đây: http://www.fileformat.info/tool/regex.htm hoặc http://gskinner.com/RegExr/


3

Regex từ Jan Goyvaerts là giải pháp tốt nhất mà tôi tìm thấy cho đến nay, nhưng cũng tạo ra các kết quả trống (null) mà anh ấy loại trừ trong chương trình của mình. Các kết quả phù hợp trống này cũng xuất hiện từ trình kiểm tra regex (ví dụ: rubular.com). Nếu bạn xoay vòng tìm kiếm (trước tiên hãy tìm các phần được trích dẫn và hơn là các từ được phân tách bằng dấu cách) thì bạn có thể thực hiện ngay một lần với:

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

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

Điều này sẽ khớp với các khoảng trắng không được bao quanh bởi dấu ngoặc kép. Tôi phải sử dụng min, max {0,99999} vì Java không hỗ trợ * và + trong lookbehind.


1

Có lẽ sẽ dễ dàng hơn để tìm kiếm chuỗi, lấy từng phần, so với chia nhỏ.

Lý do là, bạn có thể phân chia nó ở các khoảng trắng trước và sau "will be". Nhưng, tôi không thể nghĩ ra bất kỳ cách nào để chỉ định bỏ qua khoảng trống giữa bên trong phần tách.

(không phải Java thực tế)

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"
    }
}

Ngoài ra, việc ghi lại các trích dẫn đơn lẻ có thể dẫn đến các vấn đề:

"Foo's Bar 'n Grill"

//=>

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

Giải pháp của bạn không xử lý các chuỗi được trích dẫn đơn lẻ, là một phần trong ví dụ của Carl.
Jan Goyvaerts 14/12/08

1

String.split()không hữu ích ở đây vì không có cách nào để phân biệt giữa các khoảng trắng bên trong dấu ngoặc kép (không tách) và các khoảng trắng bên ngoài (tách). Matcher.lookingAt()có lẽ là những gì bạn cần:

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);
    }
}

tạo ra kết quả sau:

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

Tôi thích cách tiếp cận của Marcus, tuy nhiên, tôi đã sửa đổi nó để tôi có thể cho phép văn bản gần dấu ngoặc kép và hỗ trợ cả ký tự "và 'trích dẫn. Ví dụ: tôi cần một =" một số giá trị "để không chia nó thành [a =," một số giá trị "].

(?<!\\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

Cách tiếp cận của Jan rất tuyệt nhưng đây là một cách khác cho kỷ lục.

Nếu bạn thực sự muốn tách như đã đề cập trong tiêu đề, giữ các dấu ngoặc kép trong "will be"'regular expression', thì bạn có thể sử dụng phương pháp này thẳng ra khỏi Khớp (hoặc thay thế) một mẫu ngoại trừ trong các tình huống s1, s2, s3, v.v.

Các regex:

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

Hai lần thay thế bên trái khớp hoàn thành 'quoted strings'"double-quoted strings". Chúng tôi sẽ bỏ qua những trận đấu này. Bên phải đối sánh và chiếm khoảng trắng với Nhóm 1 và chúng tôi biết chúng là khoảng trắng bên phải vì chúng không khớp với các biểu thức ở bên trái. Chúng tôi thay thế những cái đó bằng SplitHeresau đó tách ra SplitHere. Một lần nữa, đây là trường hợp phân chia thực sự mà bạn muốn "will be", không phải will be.

Đây là một triển khai hoạt động đầy đủ (xem kết quả trên bản demo trực tuyến ).

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

Nếu bạn đang sử dụng c #, bạn có thể sử dụng

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);

Tôi đã thêm cụ thể " | <(? [\ W \ s] *)> " để làm nổi bật rằng bạn có thể chỉ định bất kỳ ký tự nào cho các cụm từ nhóm. (Trong trường hợp này, tôi đang sử dụng <> để nhóm.

Đầu ra là:

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

0

Tôi hoàn toàn chắc chắn rằng điều này là không thể nếu chỉ sử dụng biểu thức chính quy. Kiểm tra xem có thứ gì đó được chứa bên trong một số thẻ khác hay không là một thao tác phân tích cú pháp. Điều này có vẻ giống như vấn đề tương tự khi cố gắng phân tích cú pháp XML bằng regex - nó không thể được thực hiện chính xác. Bạn có thể có được kết quả mong muốn của mình bằng cách liên tục áp dụng một regex không tham lam, không toàn cục phù hợp với các chuỗi được trích dẫn, sau đó khi bạn không thể tìm thấy bất kỳ thứ gì khác, hãy chia nó tại các khoảng trắng ... có một số các vấn đề, bao gồm theo dõi thứ tự ban đầu của tất cả các chuỗi con. Đặt cược tốt nhất của bạn là chỉ cần viết một hàm thực sự đơn giản lặp lại chuỗi và lấy ra các mã thông báo bạn muốn.


Có thể với regex, hãy xem một số mẫu tôi đã liên kết. Có một vài biến thể về vấn đề này và tôi đã thấy một số câu hỏi tương tự trên SO giải quyết vấn đề này thông qua biểu thức chính quy.
Jay

1
Biết khi nào không sử dụng regex là kiến ​​thức hữu ích hơn để có thể tạo (?: (['"]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
Rene

0

Một vài chỉnh sửa hy vọng hữu ích về câu trả lời được chấp nhận của Jan:

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • Cho phép các dấu ngoặc kép thoát trong các chuỗi được trích dẫn
  • Tránh lặp lại mẫu cho dấu ngoặc kép và đơn; điều này cũng đơn giản hóa việc thêm nhiều ký hiệu trích dẫn hơn nếu cần (với chi phí của một nhóm chụp ảnh khác)

Đây phá vỡ lời với dấu nháy trong họ, giống nhưyou're
Thiết kế bởi Adrian

0

Bạn cũng có thể thử điều này:

    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]);
        }
    }

Bạn thực sự nên thêm một số giải thích về lý do tại sao điều này nên hoạt động - bạn cũng có thể thêm mã cũng như các nhận xét trong chính mã - ở dạng hiện tại, nó không cung cấp bất kỳ giải thích nào có thể giúp phần còn lại của cộng đồng hiểu được điều gì bạn đã làm để giải quyết / trả lời câu hỏi. Điều này đặc biệt quan trọng đối với những câu hỏi đã có sẵn câu trả lời.
ishmaelMakitla

0

Phần sau trả về một mảng đối số. Đối số là biến 'lệnh' phân chia trên khoảng trắng, trừ khi được bao gồm trong dấu ngoặc kép hoặc đơn. Các kết quả phù hợp sau đó được sửa đổi để loại bỏ dấu ngoặc kép và đơn.

using System.Text.RegularExpressions;

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

2
Bạn có thể giải thích thêm một chút cho câu trả lời của mình để người khác dễ hiểu hơn không? Lý tưởng nhất là chúng tôi muốn tránh các câu trả lời chỉ có mã.
Jaquez

0

Một lớp lót đầu tiên sử dụng 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.]

không tách ở khoảng trống, nếu khoảng trống được bao quanh bởi dấu nháy đơn hoặc dấu ngoặc kép thì
tách ở khoảng trống khi 255 ký tự ở bên trái và tất cả các ký tự ở bên phải của khoảng trống không phải là dấu nháy đơn hoặc dấu ngoặc kép

phỏng theo bài gốc (chỉ xử lý dấu ngoặc kép)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.