Regex zum Teilen einer Zeichenfolge mit Leerzeichen, wenn sie nicht in einfache oder doppelte Anführungszeichen gesetzt ist


113

Ich bin neu in regulären Ausdrücken und würde mich über Ihre Hilfe freuen. Ich versuche, einen Ausdruck zusammenzustellen, der die Beispielzeichenfolge unter Verwendung aller Leerzeichen aufteilt, die nicht in einfache oder doppelte Anführungszeichen gesetzt sind. Mein letzter Versuch sieht so aus: (?!")und funktioniert nicht ganz. Es teilt sich auf dem Platz vor dem Zitat auf.

Beispieleingabe:

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

Gewünschte Ausgabe:

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

Beachten Sie dies "will be"und 'regular expression'behalten Sie den Abstand zwischen den Wörtern bei.


Verwenden Sie tatsächlich die "split" -Methode oder würde eine Schleife mit der "find" -Methode in Matcher ausreichen?
Erickson

9
"und jetzt hat er zwei Probleme"

Antworten:


250

Ich verstehe nicht, warum alle anderen so komplexe reguläre Ausdrücke oder so langen Code vorschlagen. Im Wesentlichen möchten Sie zwei Arten von Dingen aus Ihrer Zeichenfolge herausholen: Zeichenfolgen, die keine Leerzeichen oder Anführungszeichen sind, und Zeichenfolgen, die mit einem Anführungszeichen ohne dazwischen liegende Anführungszeichen beginnen und enden, für zwei Arten von Anführungszeichen. Sie können diese Dinge leicht mit diesem regulären Ausdruck abgleichen:

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

Ich habe die Erfassungsgruppen hinzugefügt, weil Sie die Anführungszeichen nicht in der Liste haben möchten.

Dieser Java-Code erstellt die Liste, fügt die Erfassungsgruppe hinzu, wenn sie übereinstimmt, um die Anführungszeichen auszuschließen, und fügt die allgemeine Regex-Übereinstimmung hinzu, wenn die Erfassungsgruppe nicht übereinstimmt (ein nicht zitiertes Wort wurde abgeglichen).

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

Wenn es Ihnen nichts ausmacht, die Anführungszeichen in der zurückgegebenen Liste zu haben, können Sie viel einfacheren Code verwenden:

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, danke für deine Antwort. Übrigens bin ich ein großer Fan von EditPad.
Carlsz

Was ist, wenn ich maskierte Anführungszeichen in den Zeichenfolgen zulassen möchte \"?
Monstieur

3
Das Problem mit dieser Antwort ist mit unübertroffenem Zitat: John's motherErgebnisse aufgeteilt in[John, s, mother]
leonbloy

2
Um das Problem zu beheben, können Sie die Operanden ein wenig neu anordnen und die Anführungszeichen aus der Whitespace-Gruppe weglassen : "([^"]*)"|'([^']*)'|[^\s]+.
Ghostkeeper

1
Aufbauend auf dieser und anderen Antworten ermöglicht der folgende reguläre Ausdruck das Entkommen von Zeichen in Anführungszeichen : "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+. Siehe stackoverflow.com/questions/5695240/…
Limnic

15

Es gibt mehrere Fragen zu StackOverflow, die dieselbe Frage in verschiedenen Kontexten mit regulären Ausdrücken behandeln. Zum Beispiel:

UPDATE : Beispiel-Regex für Zeichenfolgen mit einfachen und doppelten Anführungszeichen. Ref: Wie kann ich eine Zeichenfolge teilen, außer in Anführungszeichen?

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

Getestet mit einem schnellen Perl-Snippet und die Ausgabe war wie unten wiedergegeben. Funktioniert auch für leere Zeichenfolgen oder Nur-Leerzeichen-Zeichenfolgen, wenn sie zwischen Anführungszeichen stehen (nicht sicher, ob dies gewünscht wird oder nicht).

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

Beachten Sie, dass dies die Anführungszeichen selbst in den übereinstimmenden Werten enthält. Sie können diese jedoch durch Ersetzen einer Zeichenfolge entfernen oder den regulären Ausdruck so ändern, dass sie nicht enthalten sind. Ich lasse das vorerst als Übung für den Leser oder ein anderes Poster, da 2 Uhr morgens viel zu spät ist, um sich nicht mehr mit regulären Ausdrücken zu beschäftigen;)


Ich denke, Ihre Regex erlaubt nicht übereinstimmende Anführungszeichen, z. B. "wird" und "reguläre Ausdrücke".
Zach Scrivena

@ Zach - Sie haben Recht, es tut ... aktualisiert, um das für alle Fälle zu beheben
Jay

5

Wenn Sie maskierte Anführungszeichen in der Zeichenfolge zulassen möchten, können Sie Folgendes verwenden:

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

In Anführungszeichen gesetzte Zeichenfolgen sind Gruppe 2, einzelne nicht in Anführungszeichen gesetzte Wörter sind Gruppe 3.

Sie können es hier an verschiedenen Zeichenfolgen ausprobieren: http://www.fileformat.info/tool/regex.htm oder http://gskinner.com/RegExr/


3

Der Regex von Jan Goyvaerts ist die beste Lösung, die ich bisher gefunden habe, erstellt aber auch leere (Null-) Übereinstimmungen, die er in seinem Programm ausschließt. Diese leeren Übereinstimmungen werden auch von Regex-Testern (z. B. rubular.com) angezeigt. Wenn Sie die Suche umdrehen (suchen Sie zuerst nach den zitierten Teilen und dann nach den durch Leerzeichen getrennten Wörtern), können Sie dies einmal tun mit:

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

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

Dies entspricht den Leerzeichen, die nicht in doppelte Anführungszeichen gesetzt sind. Ich muss min, max {0,99999} verwenden, da Java * und + im Lookbehind nicht unterstützt.


1

Es wird wahrscheinlich einfacher sein, die Zeichenfolge zu durchsuchen, jedes Teil zu greifen, als es zu teilen.

Grund dafür ist, dass Sie es an den Stellen davor und danach aufteilen lassen können "will be" . Ich kann mir jedoch keine Möglichkeit vorstellen, den Abstand zwischen einem Split zu ignorieren.

(nicht aktuelles 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"
    }
}

Das Erfassen einfacher Anführungszeichen kann außerdem zu Problemen führen:

"Foo's Bar 'n Grill"

//=>

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

Ihre Lösung verarbeitet keine Zeichenfolgen in einfachen Anführungszeichen, die Teil von Carls Beispiel sind.
Jan Goyvaerts

1

String.split()ist hier nicht hilfreich, da es keine Möglichkeit gibt, zwischen Leerzeichen innerhalb von Anführungszeichen (nicht teilen) und Leerzeichen außerhalb (in Teilen) zu unterscheiden. Matcher.lookingAt()ist wahrscheinlich was Sie brauchen:

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

welches die folgende Ausgabe erzeugt:

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

Ich mochte Marcus 'Ansatz, aber ich habe ihn so modifiziert, dass ich Text in der Nähe der Anführungszeichen zulassen und sowohl "als auch' Anführungszeichen unterstützen kann. Zum Beispiel brauchte ich einen =" Wert ", um ihn nicht in [a =," aufzuteilen. ein Wert "].

(?<!\\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's Ansatz ist großartig, aber hier ist noch einer für die Aufzeichnung.

Wenn Sie tatsächlich wie im Titel erwähnt aufteilen und die Anführungszeichen in "will be"und beibehalten möchten 'regular expression', können Sie diese Methode verwenden, die direkt aus ist beibehalten möchten, können einem Muster übereinstimmt (oder dieses ersetzt), außer in den Situationen s1, s2, s3 usw.

Die Regex:

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

Die beiden linken Abwechslungen stimmen vollständig 'quoted strings'und überein "double-quoted strings". Wir werden diese Übereinstimmungen ignorieren. Die rechte Seite stimmt mit Leerzeichen der Gruppe 1 überein und erfasst diese. Wir wissen, dass es sich um die richtigen Leerzeichen handelt, da sie nicht mit den Ausdrücken auf der linken Seite übereinstimmen. Wir ersetzen diese durch SplitHeredann aufgeteilt auf SplitHere. Auch dies ist für einen echten Split-Fall, wo Sie wollen "will be", nicht will be.

Hier ist eine voll funktionsfähige Implementierung (siehe die Ergebnisse in der Online-Demo ).

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

Wenn Sie c # verwenden, können Sie verwenden

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

Ich habe speziell " | <(? [\ W \ s] *)> " hinzugefügt, um hervorzuheben, dass Sie Zeichen für Gruppenphrasen angeben können. (In diesem Fall verwende ich <> zum Gruppieren.

Ausgabe ist:

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

0

Ich bin mir ziemlich sicher, dass dies mit regulären Ausdrücken allein nicht möglich ist. Das Überprüfen, ob etwas in einem anderen Tag enthalten ist, ist eine Analyseoperation. Dies scheint das gleiche Problem zu sein wie der Versuch, XML mit einem regulären Ausdruck zu analysieren - es kann nicht korrekt durchgeführt werden. Möglicherweise können Sie das gewünschte Ergebnis erzielen, indem Sie wiederholt einen nicht gierigen, nicht globalen regulären Ausdruck anwenden, der mit den angegebenen Zeichenfolgen übereinstimmt. Wenn Sie dann nichts anderes finden, teilen Sie ihn an den Stellen auf, die eine Reihe von haben Probleme, einschließlich der Verfolgung der ursprünglichen Reihenfolge aller Teilzeichenfolgen. Am besten schreiben Sie einfach eine wirklich einfache Funktion, die über die Zeichenfolge iteriert und die gewünschten Token herauszieht.


Mit einem regulären Ausdruck ist dies möglich. Sehen Sie sich einige der Beispiele an, mit denen ich verlinkt habe. Es gibt einige Variationen davon, und ich habe einige ähnliche Fragen zu SO gesehen, die dies über reguläre Ausdrücke ansprechen.
Jay

1
Zu wissen, wann Regex nicht verwendet werden soll, ist hilfreicher als das Erstellen eines (?: (['"]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
Rene

0

Ein paar hoffentlich hilfreiche Änderungen an Jans akzeptierter Antwort:

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • Ermöglicht maskierte Anführungszeichen in Zeichenfolgen in Anführungszeichen
  • Vermeidet das Wiederholen des Musters für das einfache und doppelte Anführungszeichen. Dies vereinfacht auch das Hinzufügen weiterer Anführungszeichen bei Bedarf (auf Kosten einer weiteren Erfassungsgruppe).

Dies bricht Wörter mit Apostrophen, wieyou're
Design von Adrian

0

Sie können dies auch versuchen:

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

Sie sollten wirklich eine Erklärung hinzufügen, warum dies funktionieren sollte - Sie können auch Code sowie die Kommentare im Code selbst hinzufügen - in der aktuellen Form enthält es keine Erklärung, die dem Rest der Community helfen kann, zu verstehen, was Sie haben die Frage gelöst / beantwortet. Dies ist besonders wichtig für Fragen, die bereits beantwortet wurden.
ishmaelMakitla

0

Im Folgenden wird ein Array von Argumenten zurückgegeben. Argumente sind die Variablen 'Befehl', die auf Leerzeichen aufgeteilt sind, sofern sie nicht in einfache oder doppelte Anführungszeichen gesetzt sind. Die Übereinstimmungen werden dann geändert, um die einfachen und doppelten Anführungszeichen zu entfernen.

using System.Text.RegularExpressions;

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

2
Können Sie Ihrer Antwort ein wenig Erklärung hinzufügen, damit andere sie leichter verstehen können? Im Idealfall möchten wir nur Code-Antworten vermeiden.
Jaquez

0

1. Einzeiler mit 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.]

Teilen Sie das Leerzeichen nicht auf, wenn das Leerzeichen in einfache oder doppelte Anführungszeichen gesetzt ist
die am Leerzeichen geteilt werden, wenn die 255 Zeichen links und alle Zeichen rechts vom Leerzeichen weder einfache noch doppelte Anführungszeichen sind

angepasst vom ursprünglichen Beitrag (behandelt nur doppelte Anführungszeichen)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.