Calocybe: Regulaerer Ausdruck: Dieses Problem ist geloest - doch warum?

Hallo!

Diesmal moechte ich ein Problem posten, das ich schon geloest habe. Aber eine Frage habe ich trotzdem noch dazu. Es geht um einen regulaeren Ausdruck in Perl.
Hintergrund: Fuer meinen Counter will ich von der URL des Referrers das Protokoll und den Server entfernen. Sprich: Aus 'http://www.domain.com/directory/index.html' soll '/directory/index.html' werden.

OK, mal sehn. Das allgemeine Search/Replace-Konstrukt ist s/such/ersetz/[opt].
Wir suchen also nach ein paar Buchstaben, gefolgt von einem Doppelpunkt:        s/[a-z]+:
Dahinter zwei Schraegstriche:                                                   s/[a-z]+://
Gefolgt von beliebigen Zeichen (der Server), beendet mit einem /:               s/[a-z]+://.+/

Jetzt der Ersetzungsausdruck, das ist ein /, der mit \ maskiert werden muss, dieses eingebettet in zwei / fuer
die Perl-Syntax:                                                                s/[a-z]+://.+////
Zuletzt die Optionen fuer wiederholtes ersetzten und Ignore Case:               s/[a-z]+://.+////gi
Fertig!  (more infos fuer regulaere Ausdruecke bei <../../tgcg.htm>.)

Fertig? Noch nicht ganz. Mit 'http://www.domain.com/index.html' funktioniert das ganz gut, da wird '/index.html' zurueckgeliefert. Aber mit 'http://www.domain.com/directory/index.html' kommt auch nur '/index.html' raus! Problem: Der Ausdruck sucht immer das letzte / im String. Wie loesen. Keine Ahnung, aber ich erinnerte mich darin, dass dieses Problem im Forum schonmal aufgetreten ist. Siehe dazu <../1998_3/t00019.htm#a76> und Antwortbeitraege (alle von Stefan). Stefan erzaehlt dort, dass der Ausdruck zum Ersetzen der < > -Syntax so aussah:
    $printbody =~ s/[[Ll]ink:(.*)]/<a href="\1">\1</a>/g;
Dieser Ausdruck hatte auch immer das LETZTE ] im Text gesucht, woraus der dort zu sehende Effekt resultiert. Nach der Korrektur ist das draus geworden:
    $printbody =~ s/[[Ll]ink:(.*?)]/<a href="\1">\1</a>/g;
Einfach ein ? eingefuegt. Das ist alles?! Hab ich bei mir auch gemacht, und siehe da, es funktioniert:
    $referrer = 'http://www.domain.com/directory/index.html';
    $referrer =~ s/[a-z]+://.+?////gi;
    print $referrer;        # /directory/index.html

(BTW: Ich glaube die Unterscheidung, ob das L gross oder klein ist, hatte er vorher auch nicht drin, aber das soll hier egal sein.)
Jetzt frage ich mich nur noch: Warum? Was ist den an dem Fragezeichen so toll, dass er nicht mehr das LETZTE, sondern das NAECHSTE / sucht?
SELFHTML: Das Fragezeichen ? bedeutet in einem regulären Ausdruck: {{das Zeichen vor dem Fragezeichen} oder auch nicht}.
Das bezieht sich in diesem Fall dann wohl auf .+, also {{ein oder mehrere Zeichen} oder auch nicht}. Logisch klingt das fuer mich nicht. Ich kann mir nur vorstellen, dass der Interpreter damit ausgetrickst wird, also man sich seine interne Arbeitsweise zunutze macht, die da waere, das er im ersten Fall von hinten sucht, im zweiten Fall von vorn. Aber so richtig sauber ist das dann nicht.

Wie sieht's aus, was ist der wahre Grund?

Calocybe

  1. Hi!

    Einfach ein ? eingefuegt. Das ist alles?! Hab ich bei mir auch gemacht, und siehe da, es funktioniert:
        $referrer = 'http://www.domain.com/directory/index.html';
        $referrer =~ s/[a-z]+://.+?////gi;
        print $referrer;        # /directory/index.html

    »»

    Jetzt frage ich mich nur noch: Warum? Was ist den an dem Fragezeichen so toll, dass er nicht mehr das LETZTE, sondern das NAECHSTE / sucht?

    Regular Expressions haben normalerweise ein recht einnehmendes Wesen. Da die Verarbeitung solcher Audrücke in der Regel dadurch vorgegeben ist, daß der entsprechende String per Backtracking abgesucht wird, kommt unterm Strich dabei heraus, daß das größtmogliche Ergebnis als Treffer zurückgegeben wird ... ????

    Anders ausgedrückt: es wird erstmal versucht, soviel wie möglich in das Ergebnis zu bekommen. Für den Teilausdruck [a-z]+://.+ bedeutet das denn also, daß der gesamte String genommen wird. Nun wird versucht, das / unterzubringen. Dabei geht man dann Schritt für Schritt rückwärts, bis das / passend verwendet werden kann. In diesem Falle also das LETZTE /. Ein ? dreht dieses einnehmende Verhalten nun in der Form um, daß versucht wird sowenig wie möglich in das Ergebnis zu übernehmen. Für den Teilausdruck [a-z]+://.+? wird also z.B. http://w als Ergebnis vorgehalten. Das / wird nun von Links nach rechts gesucht.

    Da Du aber eh das letzte / behalten möchtest, kannst Du Dir natürlich auch das einnehmende Verhalten zu Nutze machen, indem Du einfach soviel wie möglich bis zum / einsammeln läßt:
    also durch [^/]+ (alles was nicht / ist).

    macht alles zusammen sowas wie:
    $referrer = 'http://www.domain.com/directory/index.html';
    $referrer =~ s/^\w+://[^/]+//;

    Das g kannst Du Dir sparen, da Du die Ersetzung nur einmal benötigst ...

    Gruß,
       Jörk

    1. Hi!

      Regular Expressions haben normalerweise ein recht einnehmendes Wesen. Da die Verarbeitung solcher Audrücke in der Regel dadurch vorgegeben ist, daß der entsprechende String per Backtracking abgesucht wird, kommt unterm Strich dabei heraus, daß das größtmogliche Ergebnis als Treffer zurückgegeben wird ... ????

      Anders ausgedrückt: es wird erstmal versucht, soviel wie möglich in das Ergebnis zu bekommen. Für den Teilausdruck [a-z]+://.+ bedeutet das denn also, daß der gesamte String genommen wird. Nun wird versucht, das / unterzubringen. Dabei geht man dann Schritt für Schritt rückwärts, bis das / passend verwendet werden kann. In diesem Falle also das LETZTE /. Ein ? dreht dieses einnehmende Verhalten nun in der Form um, daß versucht wird sowenig wie möglich in das Ergebnis zu übernehmen. Für den Teilausdruck [a-z]+://.+? wird also z.B. http://w als Ergebnis vorgehalten. Das / wird nun von Links nach rechts gesucht.

      Ja, so ungefaehr hatte ich mir das gedacht.

      Da Du aber eh das letzte / behalten möchtest, kannst Du Dir natürlich auch das einnehmende Verhalten zu Nutze machen, indem Du einfach soviel wie möglich bis zum / einsammeln läßt:
      also durch [^/]+ (alles was nicht / ist).

      macht alles zusammen sowas wie:
      $referrer = 'http://www.domain.com/directory/index.html';
      $referrer =~ s/^\w+://[^/]+//;

      Da hat der das doch glatt noch optimiert. :-)  \w+ hatte ich uebrigens absichtlich nicht verwendet, da das Protokoll eigentlich nur aus Buchstaben bestehen sollte, aber is ja egal. Das Dach halte ich fuer ueberfluessig (heisst doch Zeilenanfang?), von mir aus, kann vorher auch noch was anderes stehen.

      Das g kannst Du Dir sparen, da Du die Ersetzung nur einmal benötigst ...

      Na eben, wie bin ich eigentlich da drauf gekommen?

      Calocybe