Heinz: Regex und strpos

Hallo,

ich möchte eine Zeile für Zeile dahin gehend untersuchen, ob sie einen bestimmten Begriff enthält.

Das mache ich so:

  
if( strpos($zeile,'$suchbegriff=') !== false ) {  
echo ("gefunden");  
}  

Jetzt müsste ich aber nach einem Begriff suchen, der immer so aufgebaut ist:

$config['xyz']

Mein Ziel ist es, ein Verzeichnis rekursiv zu durchsuchen, um alle möglichen $config-Werte ausfindig zu machen.

So weit bin ich:

  
<?php  
function dir_rekursiv($verzeichnis)  
{  
    $handle =  opendir($verzeichnis);  
    while ($datei = readdir($handle))  
    {  
        if ($datei != "." && $datei != "..")  
        {  
            if (is_dir($verzeichnis.$datei)) // Wenn Verzeichniseintrag ein Verzeichnis ist  
            {  
                // Erneuter Funktionsaufruf, um das aktuelle Verzeichnis auszulesen  
                dir_rekursiv($verzeichnis.$datei.'/');  
            }  
            else  
            {  
                // Wenn Verzeichnis-Eintrag eine Datei ist, diese ausgeben  
                echo $verzeichnis.$datei.'<br />';  
// Zeilen durchlaufen  
$zeilen = file("../".$verzeichnis.$datei."", FILE_SKIP_EMPTY_LINES);  
  
foreach ($zeilen as $zeile) {  
  //  echo $zeile;  
  
if( strpos($zeile,'...hier soll die Regex rein...') !== false ) {  
// Config-Wert gefunden  
}  
  
  
  
                }  
        }  
    }  
    closedir($handle);  
}  
  
  
  
dir_rekursiv('verzeichnis'.'/');  
  
  
?>  

Heinz

  1. Hallo,

    du hast einen falschen Ansatz verfolgt. So wie ich dich verstehe, benötigst du die Position des Vorkommens in der Zeile nicht. Nimm also statt strpos preg_match
    vg ichbinich

    --
    Kleiner Tipp:
    Tofu schmeckt am besten, wenn man es kurz vor dem Servieren durch ein saftiges Steak ersetzt...
    1. Nimm also statt strpos preg_match

      vg ichbinich

      Ok, guter Vorschlag. Aber was mir fehlt, ist der reguläre Ausdruck

      • $config muß am Anfang des Strings vorkommen

      • Dann muß es mit einer eckigen öffnenenden Klammer weitergehen

      • gefolgt von einem Hochkomma

      • gefolgt von beliebig vielen Groß-/Kleinbuchstaben und/oder Ziffern sowie Unterstrich

      • gefolgt von einem Hochkomme

      • gefolgt von einer schließenden eckigen Klammer am Stringende

      Und hiervon brauchen würde ich dann den "Inhalt: 'beliebig vielen Groß-/Kleinbuchstaben und/oder Ziffern sowie Unterstrich'"

      Ist das so "in Prosa" korrekt beschrieben?

      Oder würde z.b. $config noch in

      • $-Zeichen
      • gefolgt vom String "config"

      auseinander genommen?

      Kann mir einer den Regex erklären, der das macht?

      Gruß, Heinz

      1. Tach!

        Ok, guter Vorschlag. Aber was mir fehlt, ist der reguläre Ausdruck

        • $config muß am Anfang des Strings vorkommen

        Das $ ist ein Sonderzeichen, also muss es literal verwendet mit einem \ maskiert werden.

        • Dann muß es mit einer eckigen öffnenenden Klammer weitergehen

        Auch diese ist ein Sonderzeichen, muss also wieder maskiert werden.

        • gefolgt von einem Hochkomma

        Hier musst du nur PHPs Stringverarbeitung beachten. Entweder du schließt das Suchmuster in "" ein oder du musst für PHP das ' mit \ maskieren.

        • gefolgt von beliebig vielen Groß-/Kleinbuchstaben und/oder Ziffern sowie Unterstrich

        Eine Zeichenklasse: [a-zA-Z0-9_], davon ein oder mehrere: +

        • gefolgt von einem Hochkomme

        Siehe oben.

        • gefolgt von einer schließenden eckigen Klammer am Stringende

        Die schließende Klammer ] ist nur dann ein Sonderzeichen, wenn eine eckige offen ist. Ist aber keine, weil die erste literal verwendet wurde und die Zeichenklasse bereits geschlossen ist.

        Und hiervon brauchen würde ich dann den "Inhalt: 'beliebig vielen Groß-/Kleinbuchstaben und/oder Ziffern sowie Unterstrich'"

        Dann muss die Zeichenklasse nebst dem Multiplikator + in () gesetzt werden.

        Den Inhalt findest du dann in $match[1], wenn du dem preg_match() als drittem Parameter ein $match mitgibst.

        Und außerdem brauchst du auch noch Delimiter um das gesamte Muster.

        Wenn du gut aufgepasst hat, müsste dir auffallen, dass der \ mal ein RegExp-Zeichen maskiert und mal ein PHP-Zeichen. Das kann missverständlich sein, wer nun was verbraucht/benötigt. Zuerst kommt der PHP-Parser und parst alle \ weg, für die ein nachfolgendes Zeichen defniert ist. In ''-Strings ist das nur das ' und der . Alle anderen Sequenzen, wie $ werden literal interpretiert, also durchgereicht. Somit bekommt die RegExp-Maschine in den obigen Fällen alles außer dem ' zu sehen, davon sieht es nur den ' - also alles bestens.

        dedlfix.

        1. Hi dedlfix,

          Versuch:

          ^$['config[a-zA-Z0-9_]+']$

          ^                     -> Stringanfang
          $                    -> maskiertes Dollarzeichen
          [                    -> maskierte geöffnete Eckklammer
          '                    -> maskiertes Hochkomma
          config[a-zA-Z0-9_]+   -> Ein oder mehrere Zeichen a-z, A-Z, 0-9, _ sowie Kombinationen hieraus
          '                    -> maskiertes Hochkomma
          ]                     -> unmaskierte Eckklammer (habe nicht verstanden, warum?!)
          $                     -> Stringende

          Ist das korrekt und warum nochmal genau nicht die schließende Eckklammer maskieren?

          Gruß, Heinz

          1. Hi dedlfix,

            Versuch:

            ^$['config[a-zA-Z0-9_]+']$

            Ist natürlich Unfug.

            Nochmal:

            ^$config['[a-zA-Z0-9_]+']$

            ^                     -> Stringanfang
            $                    -> maskiertes Dollarzeichen
            config                -> config
            [                    -> maskierte geöffnete Eckklammer
            '                    -> maskiertes Hochkomma
            config[a-zA-Z0-9_]+   -> Ein oder mehrere Zeichen a-z, A-Z, 0-9, _ sowie Kombinationen hieraus
            '                    -> maskiertes Hochkomma
            ]                     -> unmaskierte Eckklammer (habe nicht verstanden, warum?!)
            $                     -> Stringende

            Ist das korrekt und warum nochmal genau nicht die schließende Eckklammer maskieren?

            Gruß, Heinz

            1. Tach!

              ^$['config[a-zA-Z0-9_]+']$
              Ist natürlich Unfug.
              Nochmal:
              ^$config['[a-zA-Z0-9_]+']$

              Ist immer noch "Unfug". ^ und $ geben nicht Anfang und Ende des Musters an, sondern sagen, dass auf Anfang und Ende einer Zeile im Suchtext geprüft werden soll. Das willst du nicht, weil dein Suchbegriff irgendwo mitten in der Zeile stehen kann und auch hinten dran noch Zeugs kommt.

              Das Muster an sich, muss zwar auch eingerahmt werden, das aber mit zwei gleichen Zeichen (Ausnahmen unbeachtet gelassen). Weiterhin muss beachtet werden, dass das Delimiter-Zeichen möglichst nicht im Muster auftauchen sollte, weil es sonst einer Extra-Behandlung bedarf. / oder # oder @ kommt bei dir nicht vor, nimm eins von denen.

              config[a-zA-Z0-9_]+   -> Ein oder mehrere Zeichen a-z, A-Z, 0-9, _ sowie Kombinationen hieraus

              Hier hast du vergessen ein config zu entfernen. Außerdem möchtest du genau diesen Teil extrahieren, als musst du ihn mit () gruppieren.

              Ist das korrekt und warum nochmal genau nicht die schließende Eckklammer maskieren?

              Du kannst sie maskieren, aber notwendig ist das nicht. Eine ] ist kein Sonderzeichen, solange nicht mit [ eine Zeichenklasse eingeleitet wurde. Das erste maskierte [ leitet keine ein und die andere Zeichenklasse ist bereits abgeschlossen.

              dedlfix.

              1. [latex]Mae  govannen![/latex]

                Ist das korrekt und warum nochmal genau nicht die schließende Eckklammer maskieren?

                Du kannst sie maskieren, aber notwendig ist das nicht. Eine ] ist kein Sonderzeichen, solange nicht mit [ eine Zeichenklasse eingeleitet wurde. Das erste maskierte [ leitet keine ein und die andere Zeichenklasse ist bereits abgeschlossen.

                Ich würde sie trotzdem immer maskieren - das macht die RegExp wesentlich einfacher lesbar, weil durch die Maskierung die Bedeutung sofort klar ist. Ansonsten müßte man erst die RegExp analysieren, um die Funktion der schließenden Klammer festzustellen.

                Stur lächeln und winken, Männer!
                Kai

                --
                Wir sind die Schlumpf. Widerschlumpf ist schlumpflos. Wir werden Sie einschlumpfen.
                SelfHTML-Forum-Stylesheet
                1. Tach!

                  Du kannst sie maskieren, aber notwendig ist das nicht. Eine ] ist kein Sonderzeichen, solange nicht mit [ eine Zeichenklasse eingeleitet wurde.
                  Ich würde sie trotzdem immer maskieren - das macht die RegExp wesentlich einfacher lesbar, weil durch die Maskierung die Bedeutung sofort klar ist. Ansonsten müßte man erst die RegExp analysieren, um die Funktion der schließenden Klammer festzustellen.

                  Wenn man einen RegExp (oder ein beliebiges anderes System) verstehen will, kommt man kaum um eine Analyse herum. Natürlich kann man jede Menge unnötige Maskierungen in den Ausdruck bringen, aber ob das in jedem Fall "wesentlich" übersichtlicher wird, wage ich doch stark zu bezweifeln. Zum Beispiel gelten in einer Zeichenklasse für eine ganze Menge Zeichen andere Regeln als außerhalb. Will man nun viele Nicht-Buchstaben darin haben, wird der Ausdruck durch nicht notwendige Maskierzeichen nicht lesbarer. Um nur mal drei Zeichen zu nehmen: [\*.?] vs. [*.?]

                  dedlfix.

                  1. Hi,

                    Leider komme ich noch nicht ganz weiter, obwohl ich wirklich versuche, alles umzusetzen:

                      
                    <?php  
                    foreach (glob('{./,./subdir/}{*.php,*.txt}',GLOB_BRACE) as $filename) {  
                    echo "$filename<br>";  
                    $zeilen = file($filename, FILE_SKIP_EMPTY_LINES);  
                    foreach ($zeilen as $zeile) {  
                    # echo $zeile;  
                    $suchmuster = '/\$config\[\'([a-zA-Z0-9_]+)\'\]/';  
                    preg_match_all($suchmuster, $zeile, $treffer);  
                    }  
                    }  
                    echo('<pre>');  
                    print_r($treffer);  
                    echo('</pre>');  
                    ?>  
                    
                    

                    liefert mir leider bisher nur die Dateinamen und ein leeres Array.

                    Heinz

                    1. Tach!

                      [Der Code]
                      liefert mir leider bisher nur die Dateinamen und ein leeres Array.

                      Du fragst die Treffer ja auch erst nach dem Durchlaufen aller Zeilen ab. In $treffer sammeln sich aber nicht die Treffer, sondern es stehen nur die drin, die beim letzten Aufruf der Funktion gefunden wurden. Vermutlich hört deine Datei nicht mit einer Treffer-Zeile auf.

                      Vereinfachungen:
                      Du musst nicht jede Zeile einzeln prüfen. Da du mit preg_match_all() arbeitest, kannst du auch gleich die gesamte Datei auf einmal prüfen lassen. Nimm file_get_contents() statt file().

                      dedlfix.

                      1. Du fragst die Treffer ja auch erst nach dem Durchlaufen aller Zeilen ab. In $treffer sammeln sich aber nicht die Treffer, sondern es stehen nur die drin, die beim letzten Aufruf der Funktion gefunden wurden. Vermutlich hört deine Datei nicht mit einer Treffer-Zeile auf.

                        Ja, sowas hatte ich mir schon gedacht, aber es leider nur auf die Datei bezogen, nicht (richtigerweise) auf die Zeile.

                        Vereinfachungen:
                        Du musst nicht jede Zeile einzeln prüfen. Da du mit preg_match_all() arbeitest, kannst du auch gleich die gesamte Datei auf einmal prüfen lassen. Nimm file_get_contents() statt file().

                        Danke für den Tip. Der hört sich ähnlich gut an, wie der glob-Tip. ;-)

                        In Summe ein schöner schlanker Code, findest Du nicht?

                        Was wäre an Deinem Beispiel (das objektorientierte) eigentlich noch besser gewesen?

                        Gruß, Heinz

                        1. Tach!

                          Was wäre an Deinem Beispiel (das objektorientierte) eigentlich noch besser gewesen?

                          glob() kann auch nur ein Verzeichnis durchlaufen. Du brauchst dazu noch die Rekursivität für die Unterverzeichnisse. Schon mit dem von mir gezeigten Beispiel sparst du dir diese und hast nur noch eine foreach-Schleife. Das hilft dir schon, auch wenn du $item und den Rest meines Postings ignorierst und mit dem Filenamen in $key das file_get_contents() fütterst.

                          dedlfix.

                          1. Arghs!

                            ich schaffs derzeit nicht mehr, das Ergebnissarray zu bereinigen, kann  da mal wer drüber schauen?

                              
                            <?php  
                            $gesamt = array();  
                            foreach (glob('{./}{*.php,*.txt}',GLOB_BRACE) as $filename) {  
                            echo "$filename<br>";  
                            $zeilen = file_get_contents($filename);  
                            # echo $zeile;  
                            $suchmuster = '/\$config\[\'([a-zA-Z0-9_]+)\'\]/';  
                            preg_match_all($suchmuster, $zeilen, $treffer);  
                            foreach ($treffer as $einzeltreffer) {  
                            if (!in_array($einzeltreffer,$gesamt)) {  
                            $gesamt[] = $einzeltreffer;  
                            }  
                            }  
                            }  
                            echo('<pre>');  
                            print_r($gesamt);  
                            echo('</pre>');  
                              
                              
                            ?>  
                            
                            
                            1. Tach!

                              ich schaffs derzeit nicht mehr, das Ergebnissarray zu bereinigen, kann  da mal wer drüber schauen?

                              Bereinigen? Wie sieht das $treffer-Array aus? Mir scheint, du gehst einfach so drauflos, ohne den Inhalt zu kennen. Mach ein print_r(). Es hat zwei Keys, 0 und 1 und die enthalten wiederum Arrays. Das 0-er kannst du ignorieren, das ist das, was auf das gesamte Suchmuster passt. Das 1-er ist das was in der ersten Gruppe steht, also das innerhalb der ()-Klammern. Und nur das willst du haben. Weiterhin wirst du sicher noch die Ergebnisse der einzelnen Dateien mit array_merge() zu einem großen Array zusammenfassen und von diesem dann nur eindeutige Werte haben: array_unique().

                              dedlfix.

                              1. Hi dedlfix,

                                Das 1-er ist das was in der ersten Gruppe steht, also das innerhalb der ()-Klammern. Und nur das willst du haben. Weiterhin wirst du sicher noch die Ergebnisse der einzelnen Dateien mit array_merge() zu einem großen Array zusammenfassen und von diesem dann nur eindeutige Werte haben: array_unique().

                                Genau so hatte ich es auch zuerst gemacht. Dummerweise hatt ich aber immer das gesamte $treffer-array gemerget. Jetzt, nachdem ich nur das 1.Element gemerget habe, greift array_unique gut. Das hatte es zuvor nicht getan.

                                Vielen lieben Dank fürs "an die Hand" nehmen bis zur Lösung. Jetzt läuft das Script wie gewünscht.

                                Gruß, Heinz

  2. Tach!

    if( strpos($zeile,'$suchbegriff=') !== false ) {
    Jetzt müsste ich aber nach einem Begriff suchen, der immer so aufgebaut ist:
    $config['xyz']

    Das ist auch nur ein String, der sich problemlos mit strpos() finden lässt.

    Mein Ziel ist es, ein Verzeichnis rekursiv zu durchsuchen, um alle möglichen $config-Werte ausfindig zu machen.

    Dann nimm den RecursiveDirectoryIterator zusammen mit dem RecursiveIteratorIterator. Der erste läuft verschachtelt über ein Verzeichnis und der zweite macht es flach, so dass ein einfaches foreach reicht, um alles zu durchlaufen. Zudem kann man dem RecursiveDirectoryIterator-Konstruktor mitgeben, dass er . und .. ignoriert.

    <pre>  
    <?php  
      
    $rdi = new RecursiveDirectoryIterator(__DIR__, FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS);  
    $rii = new RecursiveIteratorIterator($rdi);  
      
    foreach ($rii as $key => $item) {  
    	echo $key, "\n";  
    	print_r($item);  
    }
    

    $item ist ein SplFileInfo-Objekt, welches eine openFile()-Methode hat, die ein SplFileObject liefert, mit dem man dann die Innereien der Datei untersuchen kann. Da SplFileObject ebenfalls einen Iterator an Bord hat, kannst du statt fgets() sogar mit foreach durch die Zeilen laufen.

    dedlfix.

    1. Hi dedlfuchs ;-)

      Das ist ja lieb und nett gemeint, aber da ich (noch) nicht objektorientiert programmiere, versteh ich natürlich nur Bahnhof.

      Und mal ehrlich, es müßte doch auch über meinen bereits geposteten Ansatz problemlos funktionieren. Den würde ich dann wenigstens auch kapieren ;-)

      Gruß, Heinz

      1. Tach!

        Das ist ja lieb und nett gemeint, aber da ich (noch) nicht objektorientiert programmiere, versteh ich natürlich nur Bahnhof.

        Du musst hier kein Programm objektorientiert aufsetzen, es reicht ein wenig Anwendungswissen. Aber wenn du von vorn herein die Klappen zu machst, lohnt es sich nicht, hier weiter zu machen. Du könntest dann jedoch wenigstens auf glob() umsteigen statt mit der Kombination opendir/readdir/if ./.. zu hantieren. Auch glob() kennt eine Möglichkeit, die ./.. direkt auszuschließen.

        Und mal ehrlich, es müßte doch auch über meinen bereits geposteten Ansatz problemlos funktionieren. Den würde ich dann wenigstens auch kapieren ;-)

        Ja, wo ist das Problem? Suchst du nach $config['xyz'] und anderen Zeichenketten, die du bereits kennst, dann reicht strpos(). Suchst du nach Mustern, dann braucht's RegExp. Formuliere dafür zunächst die Regel in normaler Sprache, das aber möglichst exakt. Bei den RegExp-Fallstricken (Gierigkeit) kann man dir dann auch konkret helfen.

        Andere Ansätze hängen davon ab, was du konkret lesen willst und was das eigentliche Ziel ist. Wenn zum Beispiel nur solche Zuweisungen und nichts anderes (außer <?php) in der Datei stehen, dann würde ich nicht selbst parsen wollen, sondern die Datei innerhalb einer Funktion inkludieren. Die Variablen werden dann nur innerhalb des Scopes der Funktion angelegt und wenn es letzlich ein Array ist, kannst du es ganz einfach zurückgeben.

        dedlfix.

        1. Hi dedlfix,

          Du könntest dann jedoch wenigstens auf glob() umsteigen statt mit der Kombination opendir/readdir/if ./.. zu hantieren. Auch glob() kennt eine Möglichkeit, die ./.. direkt auszuschließen.

          Ok. Google ich sofort nach diesem Post.

          Ja, wo ist das Problem? Suchst du nach $config['xyz'] und anderen Zeichenketten, die du bereits kennst, dann reicht strpos(). Suchst du nach Mustern, dann braucht's RegExp.

          Nein, ich kenne die anderen Zeichenketten nicht.
          Hintergrund: Ich habe beim programmieren viele Optionen eingebaut, die je nach Konfiguration verfügbar sind oder auch nicht.
          Hierzu gibt es eine Tabelle Konfigwerte in meiner db. Ich ahbe aber inwischen den Überblick über alle Konfigurationswerte verloren.

          Deshalb möchte ich alle Scripte durchlaufen und alle möglichen Konfigurationswerte heraussuchen und mir darstellen lassen.

          Im Script steht bspw. if ($config['xyz'] == 1) { ...

          Formuliere dafür zunächst die Regel in normaler Sprache, das aber möglichst exakt. Bei den RegExp-Fallstricken (Gierigkeit) kann man dir dann auch konkret helfen.

          Das habe ich schon zeitgleich gemacht, während Du auch gepostet hast.
          http://forum.de.selfhtml.org/?t=212264&m=1449574

          Gruß, Heinz