Ekspresi reguler untuk memisahkan string menggunakan spasi jika tidak diapit oleh tanda kutip tunggal atau ganda


114

Saya baru mengenal ekspresi reguler dan sangat menghargai bantuan Anda. Saya mencoba untuk mengumpulkan ekspresi yang akan memisahkan string contoh menggunakan semua spasi yang tidak dikelilingi oleh tanda kutip tunggal atau ganda. Upaya terakhir saya terlihat seperti ini: (?!")dan tidak cukup berhasil. Itu terbelah di spasi sebelum kutipan.

Contoh masukan:

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

Output yang diinginkan:

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

Perhatikan itu "will be"dan 'regular expression'pertahankan spasi di antara kata-kata.


Apakah Anda benar-benar menggunakan metode "split", atau apakah perulangan dengan metode "find" di Matcher sudah cukup?
erickson

9
"dan sekarang dia memiliki dua masalah"

Jawaban:


251

Saya tidak mengerti mengapa semua yang lain mengusulkan ekspresi reguler yang rumit atau kode yang begitu panjang. Pada dasarnya, Anda ingin mengambil dua jenis hal dari string Anda: urutan karakter yang bukan spasi atau tanda kutip, dan urutan karakter yang dimulai dan diakhiri dengan kutipan, tanpa tanda kutip di antaranya, untuk dua jenis tanda kutip. Anda dapat dengan mudah mencocokkan hal-hal tersebut dengan ekspresi reguler ini:

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

Saya menambahkan grup penangkap karena Anda tidak ingin tanda kutip dalam daftar.

Kode Java ini membangun daftar, menambahkan grup penangkap jika cocok untuk mengecualikan tanda kutip, dan menambahkan pencocokan ekspresi reguler jika grup penangkap tidak cocok (kata yang tidak dikutip cocok).

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

Jika Anda tidak keberatan memiliki tanda kutip dalam daftar yang dikembalikan, Anda dapat menggunakan kode yang lebih sederhana:

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, terima kasih atas tanggapan Anda. BTW, saya penggemar berat EditPad.
carlsz

Bagaimana jika saya ingin mengizinkan tanda kutip lolos dalam string \"?
Monstieur

3
Masalah dengan jawaban ini ada pada kutipan yang tak tertandingi: John's motherhasil dipecah di[John, s, mother]
leonbloy

2
Untuk memperbaiki garis masalah leonbloy, Anda dapat memesan ulang operan sedikit dan menghilangkan tanda kutip dari spasi-kelompok: "([^"]*)"|'([^']*)'|[^\s]+.
Ghostkeeper

1
Membangun di atas ini dan jawaban lain, regex berikut ini memungkinkan melarikan diri karakter dalam tanda kutip: "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+. Lihat stackoverflow.com/questions/5695240/…
Limnic

15

Ada beberapa pertanyaan di StackOverflow yang mencakup pertanyaan yang sama ini dalam berbagai konteks menggunakan ekspresi reguler. Misalnya:

UPDATE : Contoh regex untuk menangani string kutip tunggal dan ganda. Ref: Bagaimana saya bisa membagi string kecuali di dalam tanda kutip?

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

Menguji ini dengan cuplikan Perl cepat dan hasilnya seperti yang direproduksi di bawah ini. Juga berfungsi untuk string kosong atau string hanya spasi jika berada di antara tanda kutip (tidak yakin apakah itu diinginkan atau tidak).

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

Perhatikan bahwa ini menyertakan karakter kutipan itu sendiri dalam nilai yang cocok, meskipun Anda dapat menghapusnya dengan mengganti string, atau mengubah ekspresi reguler agar tidak menyertakannya. Saya akan meninggalkannya sebagai latihan untuk pembaca atau poster lain untuk saat ini, karena jam 2 pagi sudah terlalu terlambat untuk mengotak-atik ekspresi reguler lagi;)


Menurut saya ekspresi reguler Anda memungkinkan tanda kutip yang tidak cocok, misalnya "akan menjadi 'dan' ekspresi reguler".
Zach Scrivena

@Zach - Anda benar, memang ... memperbaruinya untuk memperbaikinya untuk berjaga-jaga
Jay


3

Regex dari Jan Goyvaerts adalah solusi terbaik yang saya temukan sejauh ini, tetapi juga membuat kecocokan kosong (null), yang dia kecualikan dalam programnya. Kecocokan kosong ini juga muncul dari penguji regex (mis. Rubular.com). Jika Anda memutar pencarian di sekitar (pertama cari bagian yang dikutip dan daripada kata yang dipisahkan spasi) maka Anda dapat melakukannya sekaligus dengan:

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

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

Ini akan cocok dengan spasi yang tidak diapit oleh tanda kutip ganda. Saya harus menggunakan min, max {0,99999} karena Java tidak mendukung * dan + di lookbehind.


1

Mungkin akan lebih mudah untuk mencari string, mengambil setiap bagian, vs. membaginya.

Alasannya, Anda dapat memisahkannya di ruang sebelum dan sesudah "will be". Tapi, saya tidak bisa memikirkan cara apa pun untuk menentukan mengabaikan ruang antara di dalam split.

(bukan Java sebenarnya)

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

Selain itu, menangkap tanda kutip tunggal dapat menyebabkan masalah:

"Foo's Bar 'n Grill"

//=>

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

Solusi Anda tidak menangani string yang dikutip tunggal, yang merupakan bagian dari contoh Carl.
Jan Goyvaerts

1

String.split()tidak membantu di sini karena tidak ada cara untuk membedakan antara spasi di dalam tanda kutip (jangan pisahkan) dan spasi di luar (pisahkan). Matcher.lookingAt()mungkin yang Anda butuhkan:

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

yang menghasilkan keluaran sebagai berikut:

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

Saya menyukai pendekatan Marcus, namun, saya memodifikasinya sehingga saya dapat mengizinkan teks di dekat tanda kutip, dan mendukung karakter "dan 'kutipan. Misalnya, saya memerlukan a =" beberapa nilai "untuk tidak membaginya menjadi [a =," beberapa nilai "].

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

Pendekatan Jan bagus tapi ini satu lagi untuk dicatat.

Jika Anda benar-benar ingin membagi seperti yang disebutkan dalam judul, dengan tetap menggunakan tanda kutip "will be"dan 'regular expression', maka Anda dapat menggunakan metode ini yang langsung dari Mencocokkan (atau mengganti) pola kecuali dalam situasi s1, s2, s3 dll.

Regex:

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

Dua pertandingan pergantian kiri selesai 'quoted strings'dan "double-quoted strings". Kami akan mengabaikan pertandingan ini. Sisi kanan cocok dan menangkap spasi untuk Grup 1, dan kita tahu itu adalah spasi yang tepat karena tidak cocok dengan ekspresi di kiri. Kami mengganti mereka dengan SplitHerekemudian membagi SplitHere. Sekali lagi, ini untuk kasus split nyata yang Anda inginkan "will be", bukan will be.

Berikut adalah implementasi yang berfungsi penuh (lihat hasilnya di demo online ).

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

Jika Anda menggunakan c #, Anda dapat menggunakan

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

Saya telah menambahkan " | <(? [\ W \ s] *)> " secara khusus untuk menyorot bahwa Anda dapat menentukan karakter apa pun ke frasa grup. (Dalam hal ini saya menggunakan <> untuk mengelompokkan.

Outputnya adalah:

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

0

Saya cukup yakin ini tidak mungkin hanya menggunakan ekspresi reguler. Memeriksa apakah sesuatu ada di dalam beberapa tag lain adalah operasi penguraian. Ini sepertinya masalah yang sama seperti mencoba mengurai XML dengan regex - ini tidak dapat dilakukan dengan benar. Anda mungkin bisa mendapatkan hasil yang Anda inginkan dengan berulang kali menerapkan regex non-serakah, non-global yang cocok dengan string yang dikutip, kemudian setelah Anda tidak dapat menemukan yang lain, pisahkan di ruang ... yang memiliki sejumlah masalah, termasuk melacak urutan asli semua substring. Taruhan terbaik Anda adalah dengan hanya menulis fungsi yang sangat sederhana yang mengulang string dan mengeluarkan token yang Anda inginkan.


Mungkin dengan regex, lihat beberapa sampel yang saya tautkan. Ada beberapa variasi dalam hal ini, dan saya telah melihat beberapa pertanyaan serupa tentang SO yang membahas ini melalui ekspresi reguler.
Jay

1
Tahu kapan tidak menggunakan regex adalah pengetahuan yang lebih berguna daripada untuk dapat membuat (?: (['"]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
Rene

0

Sepasang tweak yang semoga bermanfaat pada jawaban Jan yang diterima:

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • Mengizinkan tanda kutip lolos dalam string yang dikutip
  • Menghindari pengulangan pola untuk kutipan tunggal dan ganda; ini juga menyederhanakan penambahan lebih banyak simbol kutipan jika diperlukan (dengan mengorbankan satu kelompok penangkap lagi)

Ini mematahkan kata-kata dengan apostrof di dalamnya, sepertiyou're
Design by Adrian

0

Anda juga dapat mencoba ini:

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

Anda benar-benar harus menambahkan beberapa penjelasan mengapa ini harus berhasil - Anda juga dapat menambahkan kode serta komentar di kode itu sendiri - dalam bentuknya saat ini, tidak memberikan penjelasan apa pun yang dapat membantu komunitas lainnya untuk memahami apa yang Anda lakukan untuk memecahkan / menjawab pertanyaan tersebut. Ini sangat penting terutama untuk pertanyaan yang sudah memiliki jawaban.
ishmaelMakitla

0

Berikut ini mengembalikan larik argumen. Argumen adalah variabel 'perintah' yang dipisahkan spasi, kecuali disertakan dalam tanda kutip tunggal atau ganda. Kecocokan kemudian dimodifikasi untuk menghapus tanda kutip tunggal dan ganda.

using System.Text.RegularExpressions;

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

2
Bisakah Anda menambahkan sedikit penjelasan pada jawaban Anda sehingga orang lain dapat lebih mudah memahaminya? Idealnya, kami ingin menghindari jawaban hanya kode.
Jaquez

0

Satu baris pertama menggunakan 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.]

jangan pisahkan di bagian kosong, jika bagian kosong diapit oleh tanda kutip tunggal atau ganda,
pisahkan di bagian kosong saat 255 karakter di sebelah kiri dan semua karakter di bagian kanan tidak ada tanda kutip tunggal atau ganda

diadaptasi dari posting asli (hanya menangani tanda kutip ganda)

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.