C++: Reguläre Audrücke verstehen und umsetzen



  • Guten Tag,

    ich möchte mich in das Thema "Reguläre Audrücke" einarbeiten um dann mit RegEx in C++ (Qt) arbeiten zu können da es ein nützliches Werkzeug sein kann. Ich bekomme es alleine leider nicht hin, gute Erklärungen sind leider meist auf Englisch.

    Ich weiß zwar, das z.B. [1-9] Zahlen erlaubt zwischen 1-9 oder das bei [abc] nur die Buchstaben a, b und c zulässig sind, aber wie ich das nun in die Tat umsetze bzw. praktisch anwende weiß ich nicht.

    Ich glaube ich benötige mal ein einfaches Beispiel um das zu verstehen.

    z.B. möchte ich aus einem String einen Wert extrahieren. Beispiel:

    "Peter ist 70 Jahre alt und hat bereits Probleme sich die Schuhe anzuziehen."

    Wie bekomme ich z.B. Peters Alter herausgefiltert um es dann z.B. in einem Label oder einer MessageBox anzuzeigen? Ich muss ja eigentlich "nur" den Inhalt zwischen "Peter ist " und "Jahre alt" extrahieren. Nur weiß ich nicht genau wie ich das umsetze. Ich nehme mal an das man das Problem mit regex_search() oder einer ähnlichen Funktion lösen kann.

    Vielen Dank und freundliche Grüße



  • Du musst den zu fangenden String einklammern.

    Beispiel: /Peter ist (\d+) Jahre alt/ (wichtig hier: regex_match macht verwirrenderweise einen full_match, du musst also immer noch .* anhängen) Danach ist das \d+, also eine Ziffernfolge, in der ersten Gruppe.

    std::regex re(R"(Peter ist (\d+) Jahre alt.*)");
    std::cmatch cm;
    
    std::regex_match("Peter ist 70 Jahre alt und hat bereits Probleme sich die Schuhe anzuziehen.", cm, re)
    ergibt: (bool) true
    
    Weil true rauskommt, können wir weitermachen:
    cm.size()
    ergibt: (unsigned long) 2
    
    cm[1].str()
    ergibt: (std::__cxx11::sub_match<const char *>::string_type) "70"
    In cm[0] steht der gesamte String, in cm[1] der erste eingeklammerte Ausdruck, also hier "70"
    

    PS: Ja, ich weiß, dass es regex_search gibt. Ich will aber nicht suchen (da würde ich bool als result erwarten), sondern matchen. Von daher finde ich die Namenswahl sehr unglücklich und verwende lieber match mit .* (vorne und/oder hinten), wenn ich in "irgendwo" matchen will.



  • Was haben die drei "ergibt"-Anweisugen für eine Bedeutung? Wobei handelt es sich hierbei bei "ergibt"? Habe solche Anwweisungen noch nicht behandelt.



  • Das sind bloß Kommentare.



  • Danke für die Erläuterung. Habe ich mir fast gedacht, nur sah es mir zur sehr nach Code aus. Wäre nur unsinnig wenn man sich die Zeile

    ergibt: (std::__cxx11::sub_match<const char *>::string_type) "70";

    anschaut. Wenn ich "70" manuell eingebe, brauche ich sie ja auch nicht mir Regex ermitteln. Dann werd ich mal ein bsischen rumtesten.

    Was hat das "R" eigentlich in folgender Anweisung für eine Bedeutung?:

    std::regex re(R"(Peter ist (\d+) Jahre alt.*)");



  • Das ist dann ein "raw literal", s.a. String literal (6), damit das Backslash-Zeichen nicht selbst 'escaped' werden muß.



  • Ah, ich verstehe (glaube ich :p). Ich hätte es wahrscheinlich mit einem Doppel-Backslash versucht, was aber in diesem fall nicht funktioniert, habe es mal ausprobiert. Liegt möglicherweise daran dass Regex eine eigene "mini-Sprache" ist und es anders behandelt.

    Kennt jemand eine gute Erklärung auf deutsch?



  • @DerDaVinciKot sagte in C++: Reguläre Audrücke verstehen und umsetzen:

    Ah, ich verstehe (glaube ich :p). Ich hätte es wahrscheinlich mit einem Doppel-Backslash versucht, was aber in diesem fall nicht funktioniert, habe es mal ausprobiert.

    Doch, das funktioniert auch.
    std::regex re("Peter ist (\\d+) Jahre alt.*");
    Wird aber sehr schnell sehr unübersichtlich, wenn alles mit Backslashes voll ist - und ist eine tolle Fehlerquelle. Deswegen gibt es ja die raw-String-Literale.

    Kennt jemand eine gute Erklärung auf deutsch?

    Für Regex allgemein? Das Buch "Mastering Regular Expressions" von Jeffrey Friedl. Gibts auch in deutscher Übersetzung.
    Ansonsten kann ich die Perl-Dokumentation empfehlen: https://perldoc.perl.org/perlretut.html und https://perldoc.perl.org/perlre.html (du musst dir dann aber die Perl-spezifischen Dinge wegdenken - aber prinzipiell sollte da auch alles stehen, was man wissen muss. Allerdings in Englisch.)



  • @wob, danke für die Literaturempfehlung. Ich habe bereits eine Proble (43 Seiten) von "Reguläre Ausdrücke" von Jeffrey E. F. Friedl gelesen und bin sehr begeistert. In dem PDF-Dokument wird jedoch lediglich über egrep gesprochen. Ich erhoffe mir, das in dem Buch mehr über Regex steht. Hast Du das Buch? Wird das Thema Regex genau so umfangreich und gut besprochen wie mit egrep?

    Ich habe mal nachgesehen, es scheint mehrere Ausgaben zu geben von unterschiedichen Autoren. Vom Design sind sie alle gleich und sie sind auch vom gleichen Verlag (O'Reilly Verlag)

    Das Buch (bzw. verschiedene Ausgaben?) gibt es von unterschiedlichen Autoren was mich etwas verwirrt:
    Friedl, Jeffrey E. F. (2007), Jan Goyvaerts (2009), Michael Fitzgerald (2012),

    Für welches soll ich mich entscheiden? Für das aktuellste oder gibt es noch andere Unterschiede?

    Danke sehr



  • @DerDaVinciKot egrep ist bloß ein Tool das Regex versteht. Es dient vermutlich einfach als Anschauungsobjekt.



  • @Swordfish, heißt das, ich kann die egrep-Syntax ganz einfach auch auf Regex anwenden? Beides ist von der Syntax identisch? Oder könnte es Probleme geben?



  • Es gibt nicht "die" Regex-Syntax, es gibt verschiedene Dialekte. Was egrep verwendet weiss ich nicht. Kannst du aber sicher irgendwo nachlesen. Die Dialekte sind sich aber sehr ähnlich, und gerade einfache Dinge sind quasi überall gleich. Also allgemein... ja, sollte im grossen und ganzen schon hinhauen.

    Davon abgesehen: egrep ist ein Command-Line Tool. Und so ein Command-Line Tool bekommt nicht in jedem Fall 1:1 das gefüttert was du in der Shell eintippst. Da spielt noch das Escaping der Shell mit rein.

    Also angenommen du willst mit ner Regex nach einem Stern suchen. Dann muss du den für die Regex erstmal escapen, also \*. Wenn deine Shell jetzt ebenso \ als Escape-Character verwendet, dann musst du das nochmal escapen: \\*. Und damit die Shell weiss dass sie nicht globben soll, solltest du das ganze noch quoten: "\\*"

    D.h. "\\*" als Argument für egrep entspricht der Regex \*.



  • @hustbaer, danke für die Erläuterung! Kriegst dafür ein Salbei-Bonbon 😛 🙂

    Habe auch schon das Buch "Reguläre Ausdrücke" bekommen. Echt super erklärt!



  • @wob, das mit dem Doppelbackslash funktioniert bei mir nicht. Es wird dann der gesamte String ausgegeben.

    Und wie bekomme ich es hin, mehrere Funde auszugeben? Wenn in dem String z.B. zwei mal der selbe Text vorkommt, nur mit zwei unterschiedlichen Alter? Ich habe es bereits mit

    cm[1].str()  //erster Fund
    cm[2].str()  //zweiter Fund
    

    versucht, aber die Anweisung cm[2].str() gibt nur einen leeren String zurück.



  • @DerDaVinciKot Zeig mal ein minimales aber vollständiges Beispiel wo das Problem auftritt. Es wurde in diesem Thread einiges geschrieben/empfholen und zumindest ich bin mir jetzt nicht sicher was du davon wie umgesetzt hast/wie dein Code jetzt aussieht.



  • Das tu ich doch gern!

    std::regex re(R"(Peter ist (\d+) Jahre alt.*)");
        std::cmatch cm;
        std::regex_match("Peter ist 70 Jahre alt und hat bereits Probleme sich die Schuhe anzuziehen.Peter ist 80 Jahre alt und hat bereits Probleme sich die Schuhe anzuziehen.", cm, re);
    
        qDebug() << QString::fromStdString(cm[1].str());               
        qDebug() << QString::fromStdString(cm[2].str());
    

    Ausgabe:
    "70"
    ""
    Wie bekomme ich es hin, beide Altersangaben (70 und 80) auszugeben?



  • #include <string>
    #include <regex>
    
    int main()
    {
        std::regex re(R"(Peter ist (\d+) Jahre alt)");
        std::smatch sm;
        std::string searchText = "Peter ist 70 Jahre alt und hat bereits Probleme sich die Schuhe anzuziehen.Peter ist 80 Jahre alt und hat bereits Probleme sich die Schuhe anzuziehen.";
    
        while(std::regex_search(searchText, sm, re))
        {
            std::cout << "entire match: " << sm.str() << "\n";
            for (std::size_t i = 1; i < sm.size(); ++i)
                std::cout << "capture group " << i << ": " << sm[i].str() << "\n";
            searchText = sm.suffix();
        }
    }
    

    Pass das für deine Qt bedürfnisse an.
    Das geht sich besser, weil searchText = sm.suffix(); ist jetzt nicht das performanteste auf der Welt, aber ich hab das schnell zusammenschmissen.



  • Du kannst auch einen regex_iterator nutzen:

    std::regex re(R"(Peter ist (\d+) Jahre alt)");
    std::string s("Peter ist 70 Jahre alt. Oder: Peter ist 60 Jahre alt?");
    auto resultIt = std::sregex_iterator(s.begin(), s.end(), re);
    for (auto it = resultIt; it != std::sregex_iterator(); ++it) {
        std::cout << "Ganzer Match: |" << it->str() << "|, das Alter ist: |" << (*it)[1].str() << "|\n";
    }
    


  • Da @DerDaVinciKot Qt in seinem Projekt verwendet schmeiß ich mal in dem Raum, dass Qt auch selbst eine Regex klasse hat. https://doc.qt.io/qt-5/qregularexpression.html



  • Hey, Ihr seid echt spitze! Beide Codes funktionieren. Ich werde mich da mal durcharbeiten damit ich das ganze auch wirklich verstehe.

    Ein schönes Wochenende noch


Log in to reply