Stefan Welscher: [Regex]Vor einem Zeichen X darf kein Zeichen Y stehen

Hi,
folgendes kleines Problem....:
Ich versuche bei einem String, in dem mehrere eckige Klammern vorkommen können, diese eckigen Klammern von "innen" nach "außen" aufzulösen, dabei aber jene eckigen Klammern zu ignorieren, die mit Backslash escaped sind.

Etwas komplexeres Beispiel:
SET-CE-LAN-[SUBIF-[TEST1-{1,15}-TEST2!=200&VLAN!~2[123]{1,2}2|TEST3-{}!=201&TEST4-{}=203]-IPV6-ACTIVE=1|SUBIF-[VLAN!=200|VLAN=201]-IPV4-ACTIVE=1]-SUBIF-[SEC-{1}-HELPER-{}=1.1.1.1]-DESCRIPTION=TEST

Der Skriptteil sieht aktuell wie folgt aus:

  
my $tmp=$cmd;  
while ($tmp=~/^(.*?)(?<!\\)\[([^\[\]])*(?!\\)\](.*)\s*\r*\n*$/)  
{  
    $tmp=$1.$3;  
    $content=$2;  
}  

Leider scheint das Script nicht zu funktionieren, vermutlich weil die negativen Lookbehinds nicht nur die Zeichen direkt vor der eckigen Klammer betrachten, sondern den gesamten Ausdruck davor.
Kann ich den negativen Lookbehind irgendwie auf eine Zeichenmenge begrenzen?

Evtl. gibt es auch andere Lösungen, aber wichtig wäre eben, dass der Gesamte Teil vor der eckigen Klammer in einer Zwischenvariable steht.

Besten Dank!

  1. gudn tach!

    Ich versuche bei einem String, in dem mehrere eckige Klammern vorkommen können, diese eckigen Klammern von "innen" nach "außen" aufzulösen

    was verstehst du genau unter "aufloesen"?
    welche teilausdruecke willst du im ergbnis haben, und wie sollen die vorliegen? als lineares array oder in einer baumstruktur? vielleicht kannst du anhand deines beispiels mal das ergebnis vorgeben, damit das klarer wird.

    prost
    seth

    1. Sehr gerne...
      Ein Array oder Hash brauch ich nicht, Skalar reicht vollkommen. Die while-Schleife soll einfach eine Klammer nach der anderen abarbeiten und den abgearbeiteten Ausdruck durch das Ergebnis ersetzen. Im Prinzip ist dabei auch egal ob die Abfrage von vorne nach hinten oder von hinten nach vorne läuft, da  sich die Ausdrücke in der Klammern nicht gegenseitig beeinflussen.

      Ausgangsbeispiel:

        
      $cmd=SET-CE-LAN-[SUBIF-[TEST1-{1,15}-TEST2!=200&VLAN!~2\[123\]{1,2}2|TEST3-{}!=201&TEST4-{}=203]-IPV6-ACTIVE=1|SUBIF-[VLAN!=200|VLAN=201]-IPV4-ACTIVE=1]-SUBIF-[SEC-{1}-HELPER-{}=1.1.1.1]-DESCRIPTION=TEST  
        
      #Loop1:  
      $Ausdruck=[TEST1-{1,15}-TEST2!=200&VLAN!~2\[123\]{1,2}2|TEST3-{}!=201&TEST4-{}=203]  
      *calc*  
      $Ergebnis=111  
      $cmd=SET-CE-LAN-[SUBIF-111-IPV6-ACTIVE=1|SUBIF-[VLAN!=200|VLAN=201]-IPV4-ACTIVE=1]-SUBIF-[SEC-{1}-HELPER-{}=1.1.1.1]-DESCRIPTION=TEST  
        
      #Loop2:  
      $Ausdruck=[VLAN!=200|VLAN=201]  
      *calc*  
      $Ergebnis=222  
      $cmd=SET-CE-LAN-[SUBIF-111-IPV6-ACTIVE=1|SUBIF-222-IPV4-ACTIVE=1]-SUBIF-[SEC-{1}-HELPER-{}=1.1.1.1]-DESCRIPTION=TEST  
        
      #Loop3:  
      $Ausdruck=[SUBIF-111-IPV6-ACTIVE=1|SUBIF-222-IPV4-ACTIVE=1]  
      *calc*  
      $Ergebnis=333  
      $cmd=SET-CE-LAN-333-SUBIF-[SEC-{1}-HELPER-{}=1.1.1.1]-DESCRIPTION=TEST  
        
      #Loop4:  
      $Ausdruck=[SEC-{1}-HELPER-{}=1.1.1.1]  
      *calc*  
      $Ergebnis=444  
      $cmd=SET-CE-LAN-333-SUBIF-444-DESCRIPTION=TEST  
      
      

      An sich funktioniert das ja schon alles, bis auf, dass ich eckige Klammern nicht escapen kann um sie vom "Split" auszunehmen.
      Ich versuche grad auch mal ohne Lookbehinds zu arbeiten, aber auch da komme ich auf keinen grünen Zweig:

        
      while ($tmp=~/^(.*?(^|[^\\]))\[([^\[\]]*?|\\[\[\]])*\](.*)\s*\r*\n*$/)  
      {  
         $tmp=$1.$4;  
         $execute=$3;  
      }  
      
      
      1. gudn tach!

        Ein Array oder Hash brauch ich nicht, Skalar reicht vollkommen. Die while-Schleife soll einfach eine Klammer nach der anderen abarbeiten und den abgearbeiteten Ausdruck durch das Ergebnis ersetzen. Im Prinzip ist dabei auch egal ob die Abfrage von vorne nach hinten oder von hinten nach vorne läuft, da  sich die Ausdrücke in der Klammern nicht gegenseitig beeinflussen.

        ahaa!

        dann sollte es damit funzen. (du warst bereits ganz nah dran:)

          
        while($tmp=~/^  
            (.*)              # prefix = $1  
            (                 # to be extracted = $2  
              (?<!\\)\[       # opening bracket  
              (?:  
                [^\[\]]       # non-backet  
                |             # or  
                (?<=\\)[\[\]] # bracket prefixed by slash  
              )+  
              (?<!\\)\]       # closing bracket  
            )  
            (.*)              # postfix = $3  
            /x){  
          $tmp = $1.$3;  
          $execute = $2;  
        }  
        
        

        sollte einigermassen selbsterklaerend sein. bei komplizierteren patterns solltest du den x-modifier verwenden. dann wird whitespace ignoriert und kann fuer eine verbesserung der lesbarkeit eingesetzt werden. ausserdem ist dann konventionelles kommentieren mit # moeglich.

        prost
        seth

        1. dann sollte es damit funzen. (du warst bereits ganz nah dran:)

          Super, besten Dank!!!

          Musste mich grad noch um den überfluteten Keller kümmern, aber hab's mir jetzt mal angesehen und umgesetzt. Klappt bestens!
          Hab jetzt auch festgestellt, dass ich schon sehr nahe dran war :)
          ... aber wie das eben so ist, wenn man etwas zum ersten mal macht (in diesem Fall die Lookbehind/Lookahead-Sachen) weiß man nie so richtig wo man genau suchen muss.
          Nomals vielen Dank!
          Stefan

        2. gudn tach!

          der kram laesst sich uebrigens noch ein bissl verschoenern. seit perl 5.10 gibt's den p-modifier, siehe selfhtml-wiki: damit saehe das ganze dann so aus:

          while($tmp=~/  
              (?<!\\)\[       # opening bracket  
              (?:  
                [^\[\]]       # non-bracket  
                |             # or  
                (?<=\\)[\[\]] # bracket prefixed by backslash  
              )+  
              (?<!\\)\]       # closing bracket  
            /px){  
            $tmp = ${^PREMATCH}.${^POSTMATCH};  
            $execute = ${^MATCH};  
          }
          

          beachte, dass du hierbei voellig ohne capture groups auskommst.

          ach so, und es geht uebrigens auch ohne look-arounds:

          $tmp=~/  
            [^\\]\K\[       # opening bracket  
            (?:  
              \\[\[\]]      # bracket prefixed by backslash  
              |             # or  
              [^\[\]]       # non-bracket  
            )+  
            [^\[\]\\]\]     # closing bracket  
          /px
          

          naja, ok, "\K" ist ein spezialfall eines look-behinds, aber ohne den wuerde es halt doch wieder ein bissl unnoetig komplizierter werden.

          prost
          seth