RegEx verstehen: Wieso matcht "a|b|c" auf "abc"

  • Code
    StringRegExp("abc", "a|b|c")

    ergibt 1, wie kann das sein? Nach meinem Verständnis dürfte das Pattern nur die 3 Strings "a","b" und "c" matchen, hier reicht aber offenbar ein Substring, obwohl kein Platzhalter für den restlichen String angegeben ist (etwa .*a.*)

  • Hi,

    du suchst entweder nach dem Buchstaben a oder nach b oder nach c im Wort abc.

    Was hast du denn erwartet und warum?

  • Wie gesagt, ich matche a, nicht .*a.* - offenbar ergänzt Autoit .* auf beiden Seiten Seite automatisch bzw. interpretiert die RegEx als Substring, weil auf "dabc" ebenso die Regex "a" matcht

    Was ich erwarte und warum habe ich doch schon beschrieben - mir ist nicht klar, wie ich es anders formulieren soll. Ich versuche es dennoch nochmal

    Code
    StringRegExp("abc", "a")

    sollte False/0 sein

    Code
    StringRegExp("abc", "a.*")

    sollte True/1 sein

  • Was hast du denn erwartet und warum?

    ^^

    Nach meinem Verständnis dürfte das Pattern nur die 3 Strings "a","b" und "c" matchen, ...

    Wie vermutlich Andy auch, ist mir nicht ganz klar, worauf Du hinaus willst.

    Ein Pattern, welches nur "a", "b" und "c" matched, wäre z.B. :

    AutoIt
    Local $sPattern = "\b(a)\b|\b(b)\b|\b(c)\b"
    ConsoleWrite('"a"   = ' & StringRegExp("a", $sPattern) & @CRLF)
    ConsoleWrite('"b"   = ' & StringRegExp("b", $sPattern) & @CRLF)
    ConsoleWrite('"c"   = ' & StringRegExp("c", $sPattern) & @CRLF)
    ConsoleWrite('"abc" = ' & StringRegExp("abc", $sPattern) & @CRLF)

    Konsole :

    "a" = 1

    "b" = 1

    "c" = 1

    "abc"= 0

    86598-musashi-c64-png

    "Am Anfang wurde das Universum erschaffen. Das machte viele Leute sehr wütend und wurde allenthalben als Schritt in die falsche Richtung angesehen."

  • Da haben sich unsere Beiträge wohl überschnitten :

    Code
    StringRegExp("abc", "a")

    sollte False/0 sein

    Code
    StringRegExp("abc", "a.*")

    sollte True/1 sein

    AutoIt
    Local $sPattern
    $sPattern = "\b(a)\b"
    ConsoleWrite("===> Pattern : " & $sPattern & @CRLF)
    ConsoleWrite('"abc" = ' & StringRegExp("abc", $sPattern) & @CRLF)
    
    $sPattern = "a"
    ConsoleWrite("===> Pattern : " & $sPattern & @CRLF)
    ConsoleWrite('"abc" = ' & StringRegExp("abc", $sPattern) & @CRLF)

    86598-musashi-c64-png

    "Am Anfang wurde das Universum erschaffen. Das machte viele Leute sehr wütend und wurde allenthalben als Schritt in die falsche Richtung angesehen."

  • Das ist für mich extrem unintuitiv. Warum muss ich das angeben, was ich nicht matchen will ("alles, was kein Wort trennt")?

    Nach meinem Verständnis von RegEx sollte man explizit angeben, was man matcht und nicht, was man nicht matcht. Will sagen: Was ich in meinem Pattern nicht angebe, soll auch nicht da sein. \b vor und nach jedes Zeichen (bzw. in meinem Fall zu machtende Wort) zu setzen macht den Code extrem unleserlich und ich kann beim besten Willen nicht verstehen, was sich ein Sprachdesigner dabei gedacht hat. Schließlich ist es StringRegEx und nicht SubStringRegEx.

    Immerhin geht "\A(a|b|c)\Z". Ist zwar hässlich und wird mich irgendwann zwingen \A und \Z nachzuschlagen, wenn ich den Code überarbeite, aber scheinbar muss das in Autoit dann so. Danke.

  • Hi,

    ich verstehe deine Interpretation und warum das aus deiner Sicht unlogisch erscheint. Allerdings haben reguläre Ausdrücke verschiedene Anwendungsfälle. Im Gegensatz zu deinem Beispiel möchte man häufig eben nicht nur prüfen, ob ein gesamter String einem Muster entspricht (Ja-Nein-Entscheidung), sondern aus einem String unbekannte Teile herausfiltern / suchen.

    Kurzes Beispiel:

    Aus "Wir treffen uns morgen um 13:35 Uhr am Bahnhof." soll die Uhrzeit herausgefiltert werden. Dann ergibt es natürlich Sinn, dass mein Pattern nur den wesentlichen Teil beinhalten muss, nämlich (ganz vereinfacht) "\d{2}:\d{2}". Alles andere würde hier zu Unleserlichkeit führen. Damit hast du genau dein gewünschtes Verhalten:

    Nach meinem Verständnis von RegEx sollte man explizit angeben, was man matcht und nicht, was man nicht matcht.

    Besonders bei den Flags 1-4 ergibt diese Substring-Suche deutlich mehr Sinn. Und wie du auch selbst bemerkt hast, ist deine gewünschte Funktionalität ebenfalls umsetzbar, man muss halt nur String-Anfang und -Ende explizit angeben:

    Immerhin geht "\A(a|b|c)\Z". Ist zwar hässlich und wird mich irgendwann zwingen \A und \Z nachzuschlagen, wenn ich den Code überarbeite, aber scheinbar muss das in Autoit dann so. Danke.

    Kleine Off-Topic-Anmerkung dazu: Dieses "hässliche" Verhalten ist nicht nur AutoIt-spezifisch, das hat sich unter anderem auch in PHP oder Linux bewährt.

    Viele Grüße

    Xenon

  • An dem was Xenon schreibt, ist vieles dran.

    Eine weniger minimalistische Beschreibung Deiner Anforderung hätte auch nicht geschadet;).

    ich kann beim besten Willen nicht verstehen, was sich ein Sprachdesigner dabei gedacht hat. Schließlich ist es StringRegEx und nicht SubStringRegEx.

    Es gibt keine begründbare Verpflichtung, das Problem ausschließlich mit einem Regulären Ausdruck anzugehen. Häufig sind die vorhandenen Stringfunktionen, ggf. gemischt mit kurzen, nachvollziehbaren RegEx-Elementen, nicht die schlechtere Wahl. Der Code wird damit zwar etwas länger, bleibt aber trotzdem verständlich.

    86598-musashi-c64-png

    "Am Anfang wurde das Universum erschaffen. Das machte viele Leute sehr wütend und wurde allenthalben als Schritt in die falsche Richtung angesehen."

  • Immerhin geht "\A(a|b|c)\Z". Ist zwar hässlich und wird mich irgendwann zwingen \A und \Z nachzuschlagen, wenn ich den Code überarbeite,

    Wenn es eventuell intuitiver für dich ist dann würde auch ^ als Stringanfang und $ als Stringende gehen (solange du nicht irgendwo ein (?m) einbaust): ^[abc]$

    aber scheinbar muss das in Autoit dann so. Danke.

    Kommst du zufällig aus dem Java, C++, oder Python-Bereich?
    Bei Java wird halt explizit zwischen .matches und .find unterschieden.
    Auch C++ und Python verhält sich so (match() vs. search()).

    Diese haben da halt eine explizite Unterscheidung getroffen. Die meisten anderen regex-Implementierungen (PHP, C#, vim, sed...) hingegen verhalten sich hingegen genauso wie AutoIt, welches im Grunde nur die weit verbreitete PCRE-Engine anbietet.

    Was nun intuitiv ist oder nicht hängt dann halt schlicht nur davon ab mit welchem Konzept du zuerst in Berührung gekommen bist.

    Um die Frage zu klären was denn korrekt wäre sage ich nur dass bereits Kenneth Thompsons qed-Implementierung aus den 60ern sich bereits so verhalten hat wie AutoIt heute auch schon :P

    Wenn dich das jedoch dermaßen stört kannst du dir jedoch einfach eine entsprechende Wrapper-Funktion erstellen:

    AutoIt
    ConsoleWrite(RegExWholeString("a", "a|b|c"))
    
    Func RegExWholeString($sString, $sPattern)
        Return StringRegExp($sString, "\A(" & $sPattern & ")\z")
    EndFunc


    Und um jetzt ganz gemein zu werden muss ich dir leider mitteilen, dass auch deine Lösung \A(a|b|c)\Z nicht in jedem Fall das macht was du erhoffst.
    Nämlich dann wenn noch ein Zeilenumbruch am Ende des Strings steht. \Z stört das nicht und matcht trotzdem.
    Um das Verhalten zu umgehen müsstest du ein \z statt einem \Z verwenden: StringRegExp("a" & @CRLF, "\Aa\z")

    3 Mal editiert, zuletzt von AspirinJunkie (24. März 2021 um 06:52)

  • Danke für die Erklärungen. Das mit \Z vs \z ist mir klar geworden aus der Hilfe, aber ein guter und wichtiger Hinweis. Mich wundert btw, dass es sich mit \A und \a nicht analog verhält.

    Durch die Perspektive search vs. match wird es wesentlich verständlicher, warum das so gebaut ist.

  • Mich wundert btw, dass es sich mit \A und \a nicht analog verhält.

    Naja das eine Datei auf ein Zeilenumbruchzeichen endet kommt halt ziemlich häufig vor.
    Z.B. bei Log-Dateien wo jede Info zeilenweise rausgeschrieben wird.
    Daher macht es schon Sinn einen entsprechenden Shortcut für diesen Fall einzubauen (eben das \Z).
    Das eine Datei hingegen mit einem Zeilenumbruch anfängt ist eher atypisch und daher hat man sicherlich auf einen derartigen Shortcut verzichtet da man stattdessen einfach nur ein \R? voranstellen müsste und man hat den selben Effekt.

    Oder meintest du, dass es einer entsprechenden Logik folgend stattdessen \a anstatt \A heißen müsste?