Johnny B.: RegEx-Frage zu "'\n'" und /g

Hallo geehrtes Forum,

hier passieren merkwürdige Dinge, die ich mir nicht erklären kann. Die Aufgabenstellung ist eigentlich simpel: es gibt mehrere Durchläufe, bei denen die doppelten Vorkommen von $check nicht abgearbeitet werden sollen. Alle neuen Versionen von $check werden in $liste gesammelt für spätere Verwendung und Speicherung in einer Datenbank (mySQL). Dabei hat mich das automatisch Hinzufügen von \r an \n zu \r\n beim Speichern durch mySQL bereits bös verwirrt. Nachdem ich jedoch dieses Verhalten aufgefangen hatte, lief es trotzdem nicht so wie gedacht. Nach langem Ausprobieren habe ich diesen Teil hier extrahiert. Es hat also mit der DB gar nichts zu tun:

my $liste;  
my ( @durchlauf ) = ( 1,2,3,4 );  
foreach ( @durchlauf ) {  
    my $check = 'a'."\n";  
    if ( $liste =~ /$check/sm ){  
        print '<br>doppelt: '.$check;  
        next;  
    }  
    $liste .= $check."\n";  
    print '<br>neu: '.$check;  
    print '<br>Liste: '.$liste;  
}

In dieser Form tut es, was es soll. Ausgabe:
-------------------------------
neu: a
Liste: a

doppelt: a
doppelt: a
doppelt: a
-------------------------------

Wenn ich das Global-Flag /g hinzufüge, (wie ich es anfangs hatte), kommt diese mich völlig verwirrende Ausgabe. Wieso matcht er 'a' nicht mehr beim dritten Durchlauf? Und danach wieder doch? WTF?

if ( $liste =~ /$check/gsm ){
-------------------------------
neu: a
Liste: a

doppelt: a
neu: a
Liste: a

a

doppelt: a
-------------------------------

Ändere ich nun die Anführungszeichen bei beiden \n von doppelt " auf einfach ', erscheint das hier:
-------------------------------
neu: a\n
Liste: a\n\n
neu: a\n
Liste: a\n\na\n\n
neu: a\n
Liste: a\n\na\n\na\n\n
neu: a\n
Liste: a\n\na\n\na\n\na\n\n
-------------------------------

Auch hier verstehe ich nicht, wieso der RegEx 'a\n' nicht matcht, wo es doch genauso in $liste steht. An dieser Ausgabe ändert sich ohne das /g-Flag nichts.

Kann mir hier jemand Licht ins Dunkel der Verwirrung bringen?

Verstörte Grüße
JOhnnY

  1. Hallo!

    Ändere ich nun die Anführungszeichen bei beiden \n von doppelt " auf einfach ', erscheint das hier:

    Das ist auch völlig ok so.
    In PHP wird in einfachen Anführungszeichen nicht nach zu parsenden Teilen gesucht, sondern der Inhalt zwischen den ' als String behandelt.

    Analoges Beispiel:

    $Katze = 'Hund';
    echo 'Ich habe eine $Katze'; // Ich habe eine $Katze
    echo "Ich habe eine $Katze"; // Ich habe eine Hund

    Kennt man auch von Arrays: $array['$key'] != $array[$key].

    Mit deinem anderen Problem hab ich mich grad nicht beschäftigt.
    Es sieht ziemlich verwirrend aus was du da machst.
    Ich bin mir auch nicht im klaren darüber ob und wozu du deine Variablen unbedingt als my oder global deklarieren willst/musst.
    Vllt. liegt da ja der Hund begraben.

    Grüße, Matze

    1. Hi,

      Ändere ich nun die Anführungszeichen bei beiden \n von doppelt " auf einfach ', erscheint das hier:

      Das ist auch völlig ok so.
      In PHP wird in einfachen Anführungszeichen nicht nach zu parsenden Teilen gesucht,

      PHP ist für die Frage aber vollkommen irrelevant, da es nicht um PHP geht, sondern um Perl ...

      cu,
      Andreas

      --
      Warum nennt sich Andreas hier MudGuard?
      O o ostern ...
      Fachfragen per Mail sind frech, werden ignoriert. Das Forum existiert.
      1. Hey!

        PHP ist für die Frage aber vollkommen irrelevant, da es nicht um PHP geht, sondern um Perl ...

        Hab ich leider auch erst danach festgestellt. Ich nehm aber mal an, dass es sich bei PERL ähnlich verhält.

        Grüße, Matze

        1. moin,

          PHP ist für die Frage aber vollkommen irrelevant, da es nicht um PHP geht, sondern um Perl ...

          Hab ich leider auch erst danach festgestellt. Ich nehm aber mal an, dass es sich bei PERL ähnlich verhält.

          Ja, ganz genauso was die Quoterei betrifft! Das (schöne) Beispiel Katze, Hund funktioniert auch mit Perl ;-)

          Nur die Geltungsbereiche von Vars sind in PHP bissl anders, z.B. nicht von innen nach außen (Perl), sondern von außen nach innen (PHP).

          Hotti

  2. Hallo Johnny,

    Dabei hat mich das automatisch Hinzufügen von \r an \n zu \r\n beim Speichern durch mySQL bereits bös verwirrt.

    MySQL verändert keine Zeilenumbrüche einfach so von sich aus. Da machst Du was falsch.

    Freundliche Grüße

    Vinzenz

    1. Hallo Vinzenz,

      MySQL verändert keine Zeilenumbrüche einfach so von sich aus. Da machst Du was falsch.

      ich speichere den String $liste von folgender Form:
      aaaaaaaaaa\nbbbbbbbbbb\ncccccccccc\n

      mittels

      $sql = "UPDATE tabelle SET prozess = CONCAT( prozess, '$liste' ) WHERE id = '$foo->{id}' ";  
      $db = $db_go -> prepare( $sql );  
      $db -> execute();
      

      So kommt er dann wieder zurück

      $db = $db_go -> prepare( "SELECT * FROM tabelle WHERE aktiv = 'Y'" );  
      $db -> execute();  
      $foo = $db -> fetchrow_hashref;
      

      und $foo->{prozess} sieht dann so aus:
      aaaaaaaaaa\r\nbbbbbbbbbb\r\ncccccccccc\r\n

      Wenn nicht mySQL das \r hinzugefügt hat, wo kommt es denn dann her?

      Besten Gruß
      JOhnnY

      1. Hallo,

        MySQL verändert keine Zeilenumbrüche einfach so von sich aus. Da machst Du was falsch.

        ich speichere den String $liste von folgender Form:
        aaaaaaaaaa\nbbbbbbbbbb\ncccccccccc\n

        aaaaaaaaaa\r\nbbbbbbbbbb\r\ncccccccccc\r\n
        Wenn nicht mySQL das \r hinzugefügt hat, wo kommt es denn dann her?

        von der API (hier von Perl). MySQL verfälscht solche Daten nicht.

        Nehmen wir an, wir haben eine Tabelle example mit den Spalten id (integer) und content (VARCHAR(100)).

          
        -- Wir fügen ASCII-Zeichen 10 (0x0A), das was Du für \n hältst.  
        INSERT INTO example (  
            id,  
            content  
        )  
        VALUES (  
            5,  
            [link:http://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_char@title=CHAR](10)  
        );
        

        Query OK, 1 row affected (0.00 sec)

          
        -- und schauen uns an, was MySQL abgespeichert hat,  
        -- in Hex-Werte umgewandelt, um nicht böse überrascht zu werden,  
        -- schließlich ist ein Zeilenumbruch ein nichtdruckbares Zeichen.  
        SELECT  
            [link:http://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_hex@title=HEX](content)  
        FROM  
            example  
        WHERE  
            id = 5;
        

        HEX(content)
        ------------
        0A
        ------------
        1 row in set (0.02 sec)

        1 Byte mit dem Wert 0x0A, den Du für \n hälstst - völlig erwartungsgemäß und völlig unverfälscht.

        Ich kann mir vorstellen, dass Dir der Artikel Plattformübergreifendes Handling von Zeilenumbrüchen weiterhelfen kann.

        Freundliche Grüße

        Vinzenz

  3. Hi,

    my $liste;

    my ( @durchlauf ) = ( 1,2,3,4 );
    foreach ( @durchlauf ) {
        my $check = 'a'."\n";
        if ( $liste =~ /$check/sm ){
            print '<br>doppelt: '.$check;
            next;
        }
        $liste .= $check."\n";
        print '<br>neu: '.$check;
        print '<br>Liste: '.$liste;
    }

    
    >   
    > In dieser Form tut es, was es soll. Ausgabe:  
    > -------------------------------  
    > neu: a  
    > Liste: a  
    >   
    > doppelt: a  
    > doppelt: a  
    > doppelt: a  
    > -------------------------------  
      
    Diese Ausgabe kann nicht von obigem Script stammen, denn laut Script müßten an verschiedenen Stellen <br> ausgegeben werden.  
      
      
    
    > Wenn ich das Global-Flag /g hinzufüge, (wie ich es anfangs hatte), kommt diese mich völlig verwirrende Ausgabe. Wieso matcht er 'a' nicht mehr beim dritten Durchlauf? Und danach wieder doch? WTF?  
    >   
    > `if ( $liste =~ /$check/gsm ){`{:.language-perl}  
    > -------------------------------  
    > neu: a  
    > Liste: a  
    >   
    > doppelt: a  
    > neu: a  
    > Liste: a  
    >   
    > a  
    >   
    > doppelt: a  
    > -------------------------------  
      
    Weil Du nach einem Match die Liste nicht neu initialisierst (next). Und der Regex immer der gleiche ist.  
      
    Beim ersten Durchlauf ist $liste leer, es wird nicht gematcht, $liste wird gesetzt.  
    Beim zweiten Durchlauf wird gematcht ($liste ist ja auf $check gesetzt), $liste wird nicht geändert wegen next.  
    Beim dritten Durchlauf wird derselbe Regex \*) auf $liste angewendet - es gibt aber nur ein Vorkommen von $check in $liste, das wurde aber schon im zweiten Durchlauf "verbraucht". $liste wird wieder neu gesetzt.  
    Beim vierten Durchlauf wird wieder gematcht (neue $liste, neues $glück).  
      
      
    \*) auch wenn der Regex neu gesetzt wird, erkennt das der Regex-Compiler und erzeugt ihn m.W. nicht neu, es wird also nicht nach dem ersten Match gesucht.  
    Du könntest das Setzen von $check auch vor die Schleife setzen ...  
      
      
      
    
    > Ändere ich nun die Anführungszeichen bei beiden \n von doppelt " auf einfach ', erscheint das hier:  
    > -------------------------------  
    > neu: a\n  
    > Liste: a\n\n  
    > neu: a\n  
    > Liste: a\n\na\n\n  
    > neu: a\n  
    > Liste: a\n\na\n\na\n\n  
    > neu: a\n  
    > Liste: a\n\na\n\na\n\na\n\n  
    > -------------------------------  
      
    In Perl werden Strings in "" und '' unterschiedlich behandelt, was escapte Zeichen und Variablenexpansion betrifft.  
    "a\n" sind 2 Zeichen, 'a\n' sind 3.  
    Wenn 'a\n' im Regex // eingesetzt wird, hat der \ allerdings wieder seine Sonderbedeutung im Regex ...  
      
    cu,  
    Andreas
    
    -- 
    [Warum nennt sich Andreas hier MudGuard?](http://MudGuard.de/)  
    [O o ostern ...](http://ostereier.andreas-waechter.de/)  
      
    Fachfragen per Mail sind frech, werden ignoriert. Das Forum existiert.  
    
    
    1. Hallo Andreas,

      *) auch wenn der Regex neu gesetzt wird, erkennt das der Regex-Compiler und erzeugt ihn m.W. nicht neu, es wird also nicht nach dem ersten Match gesucht.

      Ok, Konzept verstanden! Das ist beeindruckend: dann ist 'g' so eine Art 'Elefantengedächtnis-Flag'?! Je tiefer ich in die Materie einsteige, desto mehr Ehrfurcht habe ich vor RegEx. Als Gott die Welt erschuf, nahm er einen regulären Ausdruck - und war fertig. Echt mächtig, die kleinen Dinger!

      Du könntest das Setzen von $check auch vor die Schleife setzen ...

      ja, so wie es jetzt aussieht schon. In dem ursprünglichen Script ändert sich $check innerhalb der foreach-Schleife.

      In Perl werden Strings in "" und '' unterschiedlich behandelt, was escapte Zeichen und Variablenexpansion betrifft.
      "a\n" sind 2 Zeichen, 'a\n' sind 3.
      Wenn 'a\n' im Regex // eingesetzt wird, hat der \ allerdings wieder seine Sonderbedeutung im Regex ...

      grundsätzlich weiß ich um die unterschiedlichen Behandlungen von " und ', nur in diesem Fall war es mir noch nicht klar. Ich habe ja oben _und_ unten dasselbe Quoting verwendet. Wenn ich das jetzt richtig verstanden habe, wirken die // vom Regex analog zu "". Und wenn dann 'a\n' reinkommt, wird daraus trotzdem "a\n". Also quotemeta vorschalten und alles ist gut?:

      Korrekt arbeitet diese Version:

      my $liste;  
      my ( @durchlauf ) = ( 1,2,3,4 );  
      foreach ( @durchlauf ) {  
          my $foo = 'a'.'\n';  
          my $check = quotemeta ( $foo );  
        
          if ( $liste =~ /$check/sm ){  
              print '<br>doppelt: '.$check;  
              next;  
          }  
          $liste .= $foo.'\n';  
          print '<br>neu: '.$check;  
          print '<br>Liste: '.$liste;  
      }
      

      Erhellte Grüße
      JOhnnY