Verzweifelnder: u Modifier in einem PHP regEx Ausdruck

Hallo Forum!

Ich bin kein regEx Experte und verstehe da eine kleine Sache nicht, vielleicht kann mir das wer laienverständlich erklären?!

Es geht um den Modifier "u" in einem regEx Ausdruck, der in PHP verwendet wird. Ich habe da viele Seiten zu dem Thema besucht und bis jetzt nicht wirklich verstanden, wozu der gut ist.

Auf einigen Seiten ahbe ich was davon gelesen, dass es da um die Bitlänge von Zeichen geht und um den Unicode Modus. Auf anderen Seiten steht, das müsse man verwenden, wenn das Skript und verarbeitende Werte UTF-8 Codiert sind. Wie gesagt, mich verwirrt das alles sehr.

Ich habe folgenden Testcode geschrieben:

$S_regEx_1 = '/𝒴/';
$S_regEx_2 = '/𝒴/u';

$S_string = "Haus mit einem '𝒴' an der Türe.";

$B_test_1 = preg_match($S_regEx_1, $S_string) ? TRUE : FALSE;
$B_test_2 = preg_match($S_regEx_2, $S_string) ? TRUE : FALSE;

var_dump($B_test_1);
var_dump($B_test_2);

Nachdem "𝒴" laut dieser Seite mit 4 Bytes encoded ist, würde ohne dem u Modifier der regEx nicht funktionieren. Bei meinem Beispiel bekomme ich aber in beiden Fällen ein TRUE für die Prüfung, der regEx greift also auch ohne dem u. Wozu ist das also jetzt da?

Gekommen bin ich auf dieses Thema, weil ich mich im PHP Manual über die Funktion preg_split() informieren wollte.

Und dort bin ich bei einem Kommentar darunter auf das '//u' gestoßen, was mich seither jetzt so grübeln lässt.

Ich hab kein Problem oder Fehler ... ich will das einfach nur verstehen. Vielleicht kann mir wer helfen dabei.

Danke!

Der Verzweifelnde

  1. Ich hab kein Problem oder Fehler .

    Doch :-), hast Du. Dein Test zeigt nur, ob etwas „matscht“, nicht aber was genau.

    Dann versuche mal …

    <?php
    $S_regEx_1 = '/[𝒴ö]/';
    $S_regEx_2 = '/[𝒴ö]/u';
    
    $S_string = "Haus mit einem '𝒴' und einem 'ö' und dann '𝒴ö' sowie 'ö𝒴' an der Türe.";
    
    $B_test_1 = preg_split( $S_regEx_1, $S_string );
    
    echo "Ohne /u" . PHP_EOL;
    var_dump( $B_test_1 );
    
    $B_test_2 = preg_split( $S_regEx_2, $S_string );
    
    echo  PHP_EOL . "Mit /u" . PHP_EOL;
    var_dump( $B_test_2 );
    

    Ausgaben (im Terminal):

    Ohne /u
    array(20) {
      [0]=>
      string(16) "Haus mit einem '"
      [1]=>
      string(0) ""
      [2]=>
      string(0) ""
      [3]=>
      string(0) ""
      [4]=>
      string(13) "' und einem '"
      [5]=>
      string(0) ""
      [6]=>
      string(12) "' und dann '"
      [7]=>
      string(0) ""
      [8]=>
      string(0) ""
      [9]=>
      string(0) ""
      [10]=>
      string(0) ""
      [11]=>
      string(0) ""
      [12]=>
      string(9) "' sowie '"
      [13]=>
      string(0) ""
      [14]=>
      string(0) ""
      [15]=>
      string(0) ""
      [16]=>
      string(0) ""
      [17]=>
      string(0) ""
      [18]=>
      string(10) "' an der T"
      [19]=>
      string(4) "�re."
    }
    
    Mit /u
    array(7) {
      [0]=>
      string(16) "Haus mit einem '"
      [1]=>
      string(13) "' und einem '"
      [2]=>
      string(12) "' und dann '"
      [3]=>
      string(0) ""
      [4]=>
      string(9) "' sowie '"
      [5]=>
      string(0) ""
      [6]=>
      string(15) "' an der Türe."
    }
    

    Fazit: Nimm den Modifier.

  2. Hallo,

    Ich habe folgenden Testcode geschrieben:

    $S_regEx_1 = '/𝒴/';
    $S_regEx_2 = '/𝒴/u';
    
    $S_string = "Haus mit einem '𝒴' an der Türe.";
    
    $B_test_1 = preg_match($S_regEx_1, $S_string) ? TRUE : FALSE;
    $B_test_2 = preg_match($S_regEx_2, $S_string) ? TRUE : FALSE;
    
    var_dump($B_test_1);
    var_dump($B_test_2);
    

    Nachdem "𝒴" laut dieser Seite mit 4 Bytes encoded ist, würde ohne dem u Modifier der regEx nicht funktionieren. Bei meinem Beispiel bekomme ich aber in beiden Fällen ein TRUE für die Prüfung, der regEx greift also auch ohne dem u.

    das liegt daran, dass PHP von Haus aus nichts über Zeichencodierungen weiß und Strings einfach ohne weitere Betrachtung als Bytefolgen behandelt. Das Zeichen 𝒴 ist in beiden RegExen identisch codiert, im zu durchsuchenden String auch. Also matcht das in beiden Fällen.

    Wozu ist das also jetzt da?

    Beim reinen preg_match() hat das nach meinem Verständnis keine Funktion. Das Beispiel dort bezieht sich auf preg_split, das mit dem Modifier /u keine Bytefolgen mehr aufschneidet, die gemeinsam ein UTF-8-codiertes Zeichen bilden.

    Ich hab kein Problem oder Fehler ...

    Schön. Das lesen wir hier selten. 😉

    Einen schönen Tag noch
     Martin

    --
    "Hab ich vergessen" ist oft nur ein Euphemismus für "Hab ich noch nie verstanden".
  3. Hallo Verzweifelnder,

    grundsätzlich würde ich für die Verarbeitung von Unicode die mb-Funktionen verwenden, also mb_ereg.

    Bei einfachen Dingen, wo ein byteweise Arbeitsweise genügt, kann man auch die "klassischen" Stringfunktionen verwenden.

    Aber gerade in deiner Regex, wo Du Zeichenklassen verwendest, ist mb_ereg eher angezeigt als preg_match. Es mag sein, dass ein preg_match mit u Option äquivalent ist zu mb_ereg, das weiß ich nicht. Aber wer Unicode verarbeitet, sollte möglichst vergessen, dass es die bytebasierenden Stringfunktionen überhaupt gibt.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hi,

      grundsätzlich würde ich für die Verarbeitung von Unicode die mb-Funktionen verwenden, also mb_ereg.

      kennt mb_ereg* dieselben Features wie preg*?

      In der Doku zu mb_ereg finde ich leider keinen (funktionierenden) Link zur unterstützten Syntax ...

      cu,
      Andreas a/k/a MudGuard

      1. Gekommen bin ich auf dieses Thema, weil ich mich im PHP Manual über die Funktion preg_split() informieren wollte.

        Naja. Vorliegend ist es mb_split(), welches einen pattern versteht.

        https://www.php.net/manual/en/function.mb-split.php

        1. Hi,

          Gekommen bin ich auf dieses Thema, weil ich mich im PHP Manual über die Funktion preg_split() informieren wollte.

          Naja. Vorliegend ist es mb_split(), welches einen pattern

          versteht.

          https://www.php.net/manual/en/function.mb-split.php

          Und? da steht auch nicht, welche Syntax das pattern unterstützt. Auch kein Link dazu …

          cu,
          Andreas a/k/a MudGuard

          1. Und? da steht auch nicht, welche Syntax das pattern unterstützt. Auch kein Link dazu …

            Ja. Mist. Aber für faktisch alle andere Sprachen und Programme ist der Mist derselbe. Ich denke da nur an sed, awk, [ep]{0,1}grep . Spätestens bei Quantifiern wie + oder {n,m} und bei der Ersetzung geklammerter Fundstellen gibt es immer wieder Ärger.

            Beispiel grep

            -E, --extended-regexp     MUSTER sind erweiterte reguläre Ausdrücke
            -F, --fixed-strings       MUSTER sind Zeichenketten
            -G, --basic-regexp        MUSTER sind reguläre Standardausdrücke
            -P, --perl-regexp         MUSTER sind reguläre Ausdrücke, wie Perl
            
      2. Hallo MudGuard,

        Pattern ist Pattern. Egal auf welchem Zeichensatz

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hi,

          Pattern ist Pattern. Egal auf welchem Zeichensatz

          Nein, es gibt verschiedene Regex-Engines, die verschiedene Patterns verstehen. z.B. kennt nicht jede Regex-Engine named groups. Oder kommt nicht mit einer Mischung aus named und unnamed groups zurecht. Oder …

          cu,
          Andreas a/k/a MudGuard

          1. Hallo MudGuard,

            es gibt kaum ekelhaftere Speisen als die eigenen Worte schlucken zu müssen 😕

            Nach einem Ausflug in die Historie weiß ich jetzt wieder, dass PHP 2 Pattern-Engines hatte - Posix Extended Regular Expressions - die ereg-Funktionen, und PCRE (Perl Compatible Regular Expressions). Die ereg-Funktionen sind seit PHP 7 nicht mehr enthalten, aber der Name lebt in den mb_ereg-Funktionen fort.

            Insofern war deine Frage berechtigt, welche Engine das sei. Vor allem, wo die PHPler mit PHP 7.3 wieder mal was an der Engine geändert haben: PCRE2.

            Aus anderen Quellen ziehe ich den Schluss, dass preg_match mit u Option das gleiche tun soll wie die mb_ereg-Funktionen. Und damit sollte innerhalb von PHP im Wesentlichen mein Satz stimmen: "Pattern ist Pattern".

            Außerhalb von PHP sieht die Sache natürlich ganz anders aus. Da gibts Regex-Engines wie Sand am Meer. Und möglicherweise implementieren ein paar davon sogar reguläre Ausdrücke…

            Rolf

            --
            sumpsi - posui - obstruxi
            1. @@Rolf B

              es gibt kaum ekelhaftere Speisen als die eigenen Worte schlucken zu müssen 😕

              So wie die Drehbuchautoren der Folge 1.4 von Star Trek: Discovery: Video der Szene, Standbild.

              🖖 Живіть довго і процвітайте

              --
              When the power of love overcomes the love of power the world will know peace.
              — Jimi Hendrix
    2. Hi Rolf!

      Zunächst mal danke für alle Antworten hier!

      Also mit mb_ereg() komme ich nicht ganz klar. 😟

      Da liefern mir ein und die selben regEx Ausdrücke verschiedene Ergebnisse:

      <?php
      
      mb_internal_encoding('UTF-8');
      
      // Test 1:
      $S_string = 'Hammer';
      
      $S_regEx = '~[a-z]~';
      $B_test_1 = preg_match($S_regEx, $S_string) ? TRUE : FALSE;
      $B_test_2 = mb_ereg($S_regEx, $S_string);
      
      var_dump($B_test_1); echo '<br><br>'; // Liefert TRUE
      var_dump($B_test_2); echo '<br><br>'; // Liefert FALSE
      
      // Test 2:
      $S_string = 'Hammer';
      
      $S_regEx = '[a-z]';
      $B_test_1 = preg_match($S_regEx, $S_string) ? TRUE : FALSE;
      $B_test_2 = mb_ereg($S_regEx, $S_string);
      
      var_dump($B_test_1); echo '<br><br>'; // Liefert FALSE
      var_dump($B_test_2); echo '<br><br>'; // Liefert TRUE
      
      // Test 3 mit einem regEx Ausdruck, wie ich ihn mit preg_match() bis dato verwende:
      $S_string = 'Jörg aus Frankfurt am Main';
      
      $S_regEx = '~^[a-zäöüA-ZÄÖÜ\-\s]*$~u';
      $B_test_1 = preg_match($S_regEx, $S_string) ? TRUE : FALSE;
      $B_test_2 = mb_ereg($S_regEx, $S_string);
      
      var_dump($B_test_1); echo '<br><br>'; // Liefert TRUE (wie von mir erwartet!)
      var_dump($B_test_2); echo '<br><br>'; // Liefert FALSE
      
      ?>
      

      Verwenden diese 2 Funktionen verschiedene regEx Sybtaxe? Gibt es sowas überhaupt? Ich bin wie gesagt kein regEx Experte, aber die von Dir vorgeschlagene Funktion verwirrt mich ()derzeit noch) mehr als sie mir hilft.

      Liebe Grüße

      Der Verzweifelnde

      1. Verwenden diese 2 Funktionen verschiedene regEx Sybtaxe?

        Ja.

      2. Hallo Verzweifelnder,

        *kau*, *schluck* - bah, widerlich, eigene Worte zu verzehren...

        Meine Annahme, dass es wohl Quatsch sei, in der Laufzeitumgebung einer Sprache verschiedene Regex-Engines zu haben, war wohl nicht mit der Kreativität der Lerdorfschen Mannen (und Frauen) vereinbar.

        Die mb_regex_set_options Funktion verrät, dass die mb-Funktionen gleich 8 Syntax-Alternativen im Angebot haben. Ich würde annehmen, dass da ein "Compiler" dahinter liegt, der das auf ein einheitliches internes Format überträgt und dieses dann von einer gemeinsamen Engine ausgeführt wird. Welche Syntaxvarianten möglich sind, steht hier.

        Bei mir liefert mb_regex_set_options() den Wert "pr", d.h. die mb-Funktionen verwenden per Default die Ruby-Syntax. Und die soll im Wesentlichen PCRE-kompatibel sein. Die genaue Syntax hängt vermutlich von der verwendeten oniguruma Library ab. Welche Du hast, verrät Dir phpinfo() - in meinem PHP 8.1.4 für Windos ist es

        Multibyte regex (oniguruma) version	6.9.7
        

        Oniguruma ist eine Regex-Library, die bis PHP 7.3 mit PHP ausgeliefert wurde. Seit 7.4 wird erwartet, dass sie auf dem Server vorinstalliert ist. Sagt das Handbuch.

        Das ändert nichts an der mistigen Tatsache, dass php.net sich um eine Doku der mb-Regex-Funktionen herumdrückt und einfach vorauszusetzen scheint, dass man das alles weiß.

        Die preg_... Funktionen scheinen dagegen eine "normale" PCRE-Library zu verwenden. Mein phpinfo() sagt:

        PCRE Library Version	10.39 2021-10-29
        PCRE Unicode Version	14.0.0
        

        Der Hauptunterschied zwischen PCRE- und Oniguruma-Library ist, dass PCRE die Optionen im Patternstring erwartet und Oniguruma dafür eine eigene Funktion hat, die in PHP als mb_regex_set_options angeboten wird. Deswegen bekommen die PCRE-Funktionen die Regex-Delimiter im Pattern, und die mb_-Funktionen nicht. Die Option "u" ist für die mb-Funktionen ohnehin nicht nötig.

        Also: preg_match("/[a-z]/u", $foo) entspricht mb_ereg("[a-z]", $foo).

        Welche der beiden Libs bei komplexen Regexen und/oder viel Text performanter ist, weiß ich nicht.

        Dein Pattern [a-z] matcht übrigens nicht den ganzen String, sondern nur einen Kleinbuchstaben daraus. Aber das weißt Du vermutlich selbst…

        Rolf

        --
        sumpsi - posui - obstruxi
        1. @@Rolf B

          Dein Pattern [a-z] matcht übrigens nicht den ganzen String, sondern nur einen Kleinbuchstaben daraus.

          Oder auch nicht. Zeigt her eure Füß!

          🖖 Живіть довго і процвітайте

          --
          When the power of love overcomes the love of power the world will know peace.
          — Jimi Hendrix
  4. Die Sache ist eigentlich ganz einfach. Wenn du mit UTF-8 arbeitest, den u-modifier (PCRE_UTF8) grundsätzlich aktivieren. Da z.B. hier auch unicode support für vordefinierte Zeichenklassen wie \w word characters oder Metazeichen wie \b word boundary (anchor) aktiviert wird.

    Siehe z.B. dieses Demo (tio.run)

    
    $regex_1 = '/\w/';
    $regex_2 = '/\w/u';
    
    // utf8 enkodiert
    $test_string = "𝒴";
    
    $test_1 = (bool)preg_match($regex_1, $test_string);
    $test_2 = (bool)preg_match($regex_2, $test_string);
    
    var_dump($test_1, $test_2);
    

    bool(false) bool(true)

    Oder diese Variante mit preg_split und \b.

    Es fängt aber schon beim Punkt . an, welcher mit u multibyte Zeichen matcht, sonst würden die zerteilt. Natürlich lassen sich auch Quantifier nur im richtigen Modus sinnvoll einsetzen (Beispiel).

    Im UTF-8 Modus wird übrigens erwartet, dass auch das Suchmuster in UTF-8 kodiert ist.

    Da in der Diskussion auf die mb_ Funktionen verwiesen wurde. Ich selbst würde nie auf den Funktionsumfang von PCRE(2) verzichten wollen.

    1. Hallo Jonny,

      Da in der Diskussion auf die mb_ Funktionen verwiesen wurde. Ich selbst würde nie auf den Funktionsumfang von PCRE(2) verzichten wollen.

      Wer sagt, dass Du das bei den mb-Funktionen musst? Ich habe keinen Punkt-für-Punkt Vergleich gemacht, aber Oniguruma soll doch - zumindest mit der Default-Syntax für Ruby - im Wesentlichen PCRE-kompatibel sein. Hast Du da andere Informationen?

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Hallo lieber Rolf,

        ich weiss nicht was Oniguruma alles kann, aber PCRE kann eine Menge, z.B.

        • Rekursive Suchmuster / Subroutines
        • Conditionals (Bedingungen in Suchmustern)
        • \K was man öfter mal braucht um den Anfang des Matches zurückzusetzen
        • \G um Matches an einen Startpunkt gewissermaßen zu "ketten"
        • Lookaheads und Lookbehinds (wenn auch letztere leider nur von fixer Länge)
        • sog. Backtracking Control Verbs wie z.B. (*SKIP) meist Komination mit (?!)

        Das waren jetzt nur einige Features, die mir als erstes eingefallen sind. Weiters halte ich den Unicode Support (siehe z.B. Unicode-Kategorien wie \p{L} und dergleichen) für sehr gut.

        Edit

        Habe gerade zu Oniguruma etwas nachgeblättert und es scheint auch ziemlich umfangreich zu sein! Nichtsdestotrotz bin ich - seit ich regexe - noch auf kein Problem gestoßen, welches man nicht elegant mit PCRE hätte lösen können :) Meines Erachtens ist man damit bestens bedient.