Sascha Müller: Hilfe zu Dateiverarbeitung

Hallo,

ich habe folgendes Problem:
In einem Gästebuch möchte ich eine IP-Sperre einbauen, so dass zwei oder mehrere Einträge nur nach einer bestimmten abgelaufenen Zeit geschrieben werden können. Dazu habe ich mir überlegt, ich lege eine Logdatei auf dem Server an. In dieser Datei soll ZEILENWEISE jeweils der String $textzeile zum Beispiel geschrieben werden. Dieser String setzt sich wie folgt zusammen:

my $sekunden=time();
my $aktuelle_ip=$ENV{"REMOTE_ADDR"};
my $textzeile=sekunden&&$aktuelle_ip\n;

So soll das nun Zeilenweise in dieser Datei stehen:

1022283091&&217.82.49.20
1022283092&&217.82.49.21
1022283093&&217.82.49.22

usw... Ich glaube, das Hineinschreiben bekomme ich hin, das wird nur anhängendes Schreiben sein, und da zu einer Zeit immer nur ein User das Script ausführen kann, kann es auch nur immer eine Zeile sein, also kein Problem.

Nun zu meinem Hauptproblem:
Wie bekomme ich diese Daten wieder sauber aus dieser Textdatei heraus, so dass der erste Wert (Sekunden) auch wieder numerisch ist, und sich damit rechnen lässt. Ich möchte nämlich den aktuellen Zeitwert Minus dem gespeicherten Zeitwert nehmen, das Ergebnis muss größer als 15 Minuten sein. Wenn ich nun mal Pseudo-Code schreibe:

Öffne Datei
Speichere Dateiinhalt in Array-Variable
Schließe Datei

Verarbeite Array-Variable so lange bis Ende
{
   Lese 1. Zeile und speichere in Variable1
   Hier mach jetzt meine Wunschverarbeitung
}

Im 2. Durchgang soll dann Zeile 2 verarbeitet werden usw, bis der Zeiger auf Dateiende steht.

Entschuldigt die vielleicht ganz easy Frage, ich habe leider keine Antwort auf meine Frage in SelfHTML gefunden.

In COBOL geht das leicht. ;-) Leider hilft mir das hier nicht weiter...

Vielen tausend Dank für alle Tipps und Hilfen.

MfG
Sascha Müller

  1. Hallo,
    ich hab sowas bei meinem selbstgebastelten Forum so realisiert :

    Ich setze einfach ein Cookie, welches nach 30 Sekunden wieder abläuft und frage eben beim speichern des Postings ab, ob das Cookie existiert (den Rückgabewert 1 zurückgibt) oder nicht :

    if ($action eq 'savetopic')   {
             my $topic = $query->param('topic');
             my $text = $query->param('text');
             my $name = $query->param('name');
             my $email = $query->param('email');
             my $hpaddress = $query->param('hpaddress');
             my $icq = $query->param('icq');
             my $sessioncookie = '';

    if (readsessioncookie())  {
                 print $query->redirect("$url");
                 }  else  {
                 savesessioncookie($sessioncookie);
                 savetopic($topic,$text,$name,$email,$hpaddress,$icq);
                 writecookie($name,$email,$hpaddress,$icq,$sessioncookie);
                 }

    }

    --------------------- Cookie setzen -----------------
    sub savesessioncookie   {
    my $sessioncookie = shift;
    $$sessioncookie = $query->cookie(-name => 'sessioncookie',
                                -value => 'sessioncookie',
                                -expires => '+30s',
                                -path => '/');

    }
    ------------- Cookie abfragen ---------------
    sub readsessioncookie  {
    my %cookies = fetch CGI::Cookie;
        foreach (keys %cookies)  {
           if (($cookies{$_}) =~ /sessioncookie/)  {
           return 1;
           }
        }
    }
    ---------------------------

    Der einzige Nachteil ist natürlich, daß der User Cookies aktiviert haben muss, aber ich glaube, daß es eigentlich eher unwahrscheinlich ist, daß jemand, der Cookies nicht aktiviert hat, unbedingt so viele Doppelpostings macht. Ausserdem ist diese Lösung meiner Meinung nach effizienter.

    1. Nachtrag:

      bei Cookie setzen wird natürlich das Cookie noch nicht in den Header geschrieben, sondern erst bei writecookie(). (Ich setze hier sogar 2 Cookies, sodaß die User ihre Namensangaben beim nächsten Mal nicht immer wieder neu eintippen müssen )
      sub writecookie  {
      my ($name, $email, $hpaddress, $icq, $sessioncookie) = @_;
      my $cookie = $query->cookie(-name => 'acid4uforum',
                                  -value => ["$name","$email","$hpaddress","$icq"],
                                  -expires => '+10M',
                                  -path => '/');
      print $query->header(-cookie=>[$cookie,$sessioncookie],
                           -location=>"$url");

      }

      1. Hmmm, das habe ich mir auch schon überlegt. Cookies ist aber nicht so interessant, da habe ich schon zwei gesetzt auf meiner HP. Ein bisschen Dateiverarbeitung in Perl hätte ich da jetzt wesentlich interessanter gefunden... Vielleicht kann mir noch jemand ein bisschen helfen!?

        Aber trotzdem vielen Dank für deine Bemühungen.

        MfG
        Sascha Müller

  2. Hallo,

    my $textzeile=sekunden&&$aktuelle_ip\n;

    Du meinst sicher
    my $textzeile="$sekunden&&$aktuelle_ip\n";

    Wobei ich anmerken will, daß ein Trennzeichen vollkommen ausreichen würde.

    usw... Ich glaube, das Hineinschreiben bekomme ich hin, das wird nur anhängendes Schreiben sein, und da zu einer Zeit immer nur ein User das Script ausführen kann, kann es auch nur immer eine Zeile sein, also kein Problem.

    Ganz so einfach ist es nicht. Du solltest bedenken, daß ein Webserver mehrere Requests gleichzeitig bearbeiten kann, wodurch auch potentiell gleichzeitige Zugriffe auf diese Datei erfolgen könnten. die Funktion flock() solltest Du Dir daher genauer ansehen.
    Außerdem solltest Du zum einen verhindern, daß eine IP-Adresse doppelt vorkommt, zum anderen, daß die Datei bei Zeiten auch wieder aufgeräumt wird, damit sie nicht zu groß wird.

    Wie bekomme ich diese Daten wieder sauber aus dieser Textdatei heraus, so dass der erste Wert (Sekunden) auch wieder numerisch ist, und sich damit rechnen lässt.

    In Perl gibt eis keine Datentypen, daher können Variableninhalte einmal als Zahl, ein andermal als Text verwendet werden.

    my $wasauchimmer = "1234";
    my $wasanderes = $wasauchimmer - 4;
    print $wasanderes; # gibt 1230 aus

    Öffne Datei
    Speichere Dateiinhalt in Array-Variable
    Schließe Datei

    Für Dateizugriffe solltest Du generell einmal http://selfhtml.teamone.de/cgiperl/funktionen/einausgabe.htm durchlesen, da werden die relevanten Funktionen recht gut beschrieben.
    Einzig das Einlesen einer Datei ist etwas 'versteckt'.

    Allerdings bin ich der Meinung, daß das Einlesen der Daten in ein Array nicht nötig ist, da Du sowieso nur den Inhalt einer bestimmten Zeile suchst.

    Hier einmal ein grobes Grundgerüst für das Einlesen:

    #!/usr/bin/perl -w

    use strict;
    use Fcntl ':flock';

    open(INFILE, "</pfad/zu/daten.datei") or die "kann Datei nicht öffnen $!";
    if(flock(INFILE, LOCK_SH)) #hier wied
      {
      while(<INFILE>)
        {
        # hier gehört die Bearbeitung der Zeile, welche in der Variable $_
        # gespeichert ist, hinein.
        }
      close(INFILE);
      }
    else
      {
      print "Ups, Datei konnte nicht gesperrt werden";
      }

    Entschuldigt die vielleicht ganz easy Frage, ich habe leider keine Antwort auf meine Frage in SelfHTML gefunden.

    Obiger Link sollte die meisten Fragen beantworten.

    Und was das Auswerten der Zeilen betriff, solltest Du Dir unbedingt Funktionen wie chomp() und split() ansehen.
    http://selfhtml.teamone.de/cgiperl/funktionen/zeichenketten.htm
    Ebenso den Unterschied zwischen numerischen (==) und textuellen (eq) Vergleichen.
    http://selfhtml.teamone.de/cgiperl/sprache/operatoren.htm#vergleich

    In COBOL geht das leicht. ;-) Leider hilft mir das hier nicht weiter...

    Wenn Du COBOL schon leicht findest, dan wirst Du Staunen, wie einfahc es in Perl geht;-)

    BTW.: Du weißt daß mehrere Besucher eventuell mit der selben IP-Adresse bei Dir landen können, genau so wie es innerhalb der von Dir gewählten Zeitspanne durchaus vorkommen kann, daß ein Besucher seine IP-Adresse wecheseln kann. Somit ist bei Deinem Vorhaben mit einer gewissen Unschärfe bei der Treffsicherheit zu rechnen.

    Grüße
      Klaus

    1. Hallo Klaus,

      Hallo,

      my $textzeile=sekunden&&$aktuelle_ip\n;

      Du meinst sicher
      my $textzeile="$sekunden&&$aktuelle_ip\n";

      Ja, natürlich :-)

      Wobei ich anmerken will, daß ein Trennzeichen vollkommen ausreichen würde.

      O.k., hast eigentlich auch wieder Recht...

      usw... Ich glaube, das Hineinschreiben bekomme ich hin, das wird nur anhängendes Schreiben sein, und da zu einer Zeit immer nur ein User das Script ausführen kann, kann es auch nur immer eine Zeile sein, also kein Problem.

      Ganz so einfach ist es nicht. Du solltest bedenken, daß ein Webserver mehrere Requests gleichzeitig bearbeiten kann, wodurch auch potentiell gleichzeitige Zugriffe auf diese Datei erfolgen könnten. die Funktion flock() solltest Du Dir daher genauer ansehen.

      O.k., sperren ist vielleicht nicht verkehrt, werde ich mit einbauen

      Außerdem solltest Du zum einen verhindern, daß eine IP-Adresse doppelt vorkommt, zum anderen, daß die Datei bei Zeiten auch wieder aufgeräumt wird, damit sie nicht zu groß wird.

      Beim ersten Lesedurchgang der Datei soll - bevor überhaupt etwas anderes gemacht wird - alle Zeileneinträge die älter als 15 Minuten sind, "Gelöscht" werden. Da ein direktes Löschen einer Zeile in Perl nicht möglich ist!?!?, überlese ich diese Zeilen, alle jüngeren merke ich mir, und schreibe sie später wieder zurück. Habe ich also berücksichtigt.

      Wie bekomme ich diese Daten wieder sauber aus dieser Textdatei heraus, so dass der erste Wert (Sekunden) auch wieder numerisch ist, und sich damit rechnen lässt.

      In Perl gibt eis keine Datentypen, daher können Variableninhalte einmal als Zahl, ein andermal als Text verwendet werden.

      my $wasauchimmer = "1234";
      my $wasanderes = $wasauchimmer - 4;
      print $wasanderes; # gibt 1230 aus

      Kenne ich, aber ich habe es schon geschafft, EINE Zeile aus der Datei auszulesen, habe diesen von einem anderen Wert abgezogen, und das Ergebnis war irgendwie nicht korrekt :S :
      my $Variable1="Erste Zeile aus Datei"; #Ist ein alter Timestamp --> time();
      my $Variable2=time();
      my $Variable3=$Variable2-$Variable1;

      Das wollte er nicht, er hat irgendwas gerechnet, aber das Ergebnis stimmte nicht.

      Öffne Datei
      Speichere Dateiinhalt in Array-Variable
      Schließe Datei

      Für Dateizugriffe solltest Du generell einmal http://selfhtml.teamone.de/cgiperl/funktionen/einausgabe.htm durchlesen, da werden die relevanten Funktionen recht gut beschrieben.
      Einzig das Einlesen einer Datei ist etwas 'versteckt'.

      Werde ich auf jeden Fall noch machen, aber diese ausführliche Antwort von dir, hat auf jeden Fall mal eine schnelle Antwort verdient. :-)

      Allerdings bin ich der Meinung, daß das Einlesen der Daten in ein Array nicht nötig ist, da Du sowieso nur den Inhalt einer bestimmten Zeile suchst.

      Wie gesagt, alle Einträge die älter als 15 Minuten sind, überlesen, deshalb muss ich jede Zeile verarbeiten, zumindest jeden "Timestamp" kurz prüfen" ;-)

      Hier einmal ein grobes Grundgerüst für das Einlesen:

      #!/usr/bin/perl -w

      use strict;
      use Fcntl ':flock';

      open(INFILE, "</pfad/zu/daten.datei") or die "kann Datei nicht öffnen $!";
      if(flock(INFILE, LOCK_SH)) #hier wied
        {
        while(<INFILE>)
          {
          # hier gehört die Bearbeitung der Zeile, welche in der Variable $_
          # gespeichert ist, hinein.
          }
        close(INFILE);
        }
      else
        {
        print "Ups, Datei konnte nicht gesperrt werden";
        }

      Entschuldigt die vielleicht ganz easy Frage, ich habe leider keine Antwort auf meine Frage in SelfHTML gefunden.

      Obiger Link sollte die meisten Fragen beantworten.

      Und was das Auswerten der Zeilen betriff, solltest Du Dir unbedingt Funktionen wie chomp() und split() ansehen.

      Ja, da war ich schon, wird schon noch...

      http://selfhtml.teamone.de/cgiperl/funktionen/zeichenketten.htm
      Ebenso den Unterschied zwischen numerischen (==) und textuellen (eq) Vergleichen.

      Hör mir auf du! Da habe ich schon vor etwas längerer Zeit einen halben Tag lang Fehler gesucht. Aber charakter Werte werden eben mit eq verglichen, und dann funktioniert das auch. ;-)

      http://selfhtml.teamone.de/cgiperl/sprache/operatoren.htm#vergleich

      In COBOL geht das leicht. ;-) Leider hilft mir das hier nicht weiter...

      Wenn Du COBOL schon leicht findest, dan wirst Du Staunen, wie einfahc es in Perl geht;-)

      Na ja, in COBOL sieht das so aus:

      Definiere Datei als E01
      Definiere Datenklotz (Struktur) der genau die Länge einer Zeile der Datei hat, in meinem Fall:

      01 Struktur
         05 Timestamp PIC 9(10) value zero.
         05 FILLER    PIC X(02).
         05 IP-ADR    PIC X(15).

      perform until EOF = 1
         read E01 into Struktur
         at end move 1 to EOF
      end-perform

      Nun habe ich meine Daten sauberst in Timestamp und in IP-ADR. IP-ADR hätte ich sogar noch weiter nach unten verteilen können, mit Punkten und so, aber belassen wir es mal hierbei...

      BTW.: Du weißt daß mehrere Besucher eventuell mit der selben IP-Adresse bei Dir landen können, genau so wie es innerhalb der von Dir gewählten Zeitspanne durchaus vorkommen kann, daß ein Besucher seine IP-Adresse wecheseln kann. Somit ist bei Deinem Vorhaben mit einer gewissen Unschärfe bei der Treffsicherheit zu rechnen.

      Habe ich mir auch schon überlegt :S Wie groß aber ist die Wahrscheinlichkeit, dass innerhalb von 15 Minuten jemand auflegt, ein anderer sich wieder einwählt, genau des ersten Mann IP-Adresse bekommt, zufällig auf meine Homepage geht, und zufällig in mein Gästebuch schreiben möchte :S Bis diese ganzen Zufälle eintreten, ist außerdem schon wahrscheinlich so viel Zeit vergangen, dass die 15 Minuten vorbei sind.
      Stell dir folgenden Zeitablauf vor:

      10:00 Uhr: Er schreibt in mein Gästebuch
      10:01 Uhr: Er betrachtet seinen schönen Eintrag
      10:02 Uhr: Er betrachtet ihn immer noch
      10:05 Uhr: Er verlässt meine Homepage
      10:06 Uhr: Jetzt hat er auch die anderen 20 Browserfenster geschlossen und legt auf, nahezu zeitgleich, nämlich um
      10:06 Uhr: Zweiter User wählt sich ein, bekommt die IP-Adresse des ersten Users
      10:07 Uhr: Ganz zufällig gibt auch diese Uhser die URL zu meiner HP ein
      10:08 Uhr: Er studiert meine HP
      10:10 Uhr: Er liest Gästebucheinträge
      10:11 Uhr: Er möchte auch einen GB-Eintrag schreiben :S *arg*

      O.k., o.k., ich setze die Zeit auf 10 Minuten, aber das sollte dann doch passen

      Ansonsten "mehrere Besucher mit der gleichen IP"!? Das ginge nur noch über ein Firmennetzwerk. Na ich will mal nicht hoffen, dass 300 Mitarbeiter zeitgleich alle in mein Gästebuch schreiben. *lol* Hilfä!!!

      Wenn ein Spammer wieder auflegt und sich neu einwählt hat er die IP-Adresse gewechselt, diese Anmerkung von dir ist vollkommen korrekt. Deshalb war meine Überlegung auch schon, diese IP-Sperre mit einem Cookie zu koppeln. Aber ich habe jetzt für mich entschlossen, dass das vorerst reicht, ich werde dann sehen, ob immer noch gespammt wird, oder nicht. Aber ich denke mal, es ist zu mühselig sich ständig aus- und wieder einzuwählen.

      Grüße
        Klaus

      Ansonsten 1000 Dank für deine sicherlich brauchbaren Tipps und Ratschläge. Ich werde mich jetzt an die Arbeit machen. Ich bin mir sicher, dass ich es mit dieser Hilfestellung hinbekomme.

      Mit freundlichen Grüßen
      Sascha Müller

      1. Hallo,

        Beim ersten Lesedurchgang der Datei soll - bevor überhaupt etwas anderes gemacht wird - alle Zeileneinträge die älter als 15 Minuten sind, "Gelöscht" werden. Da ein direktes Löschen einer Zeile in Perl nicht möglich ist!?!?, überlese ich diese Zeilen, alle jüngeren merke ich mir, und schreibe sie später wieder zurück. Habe ich also berücksichtigt.

        Einlesen, bzw. überlesen, und dann Schreiben ist IMHO die übliche Vorgangsweise.

        my $Variable1="Erste Zeile aus Datei"; #Ist ein alter Timestamp --> time();

        Bedenke, daß Deine Zeile zuerst analysiert und in die Einzelwerte zerlegt sein will. Dafür kannst Du eben chomp() (Entfernt Zeilentrennzeichen) und split() (Teilt an den Feldtrennzeichen und liefert eine Liste der ermittelten Werte) verwenden.

        Na ja, in COBOL sieht das so aus:

        [...]

        IMHO solltest DU aber nicht probieren, COBOL-ähnliche Konstrukte mit Perl zu 'simulieren'. Es ist immer besser sich auf das Wesen und die Eigenarten einer Sprache einzulassen. Ich habe das auch schon durchgemacht, daß ich probiert habe, in einer Sprache etwas so zu formulieren, wie ich es in einer anderen gewohnt war. Im Normalfall kommen dann irgendwelche absonderlichen Dinge heraus, die im betsen Falle gerade noch funktionieren, von Eleganz bzw. Performance ist dann wiet und breit nichts mehr zu sehen.

        Habe ich mir auch schon überlegt :S Wie groß aber ist die Wahrscheinlichkeit, dass innerhalb von 15 Minuten jemand auflegt, ein anderer sich wieder einwählt, genau des ersten Mann IP-Adresse bekommt, zufällig auf meine Homepage geht, und zufällig in mein Gästebuch schreiben möchte :S Bis diese ganzen Zufälle eintreten, ist außerdem schon wahrscheinlich so viel Zeit vergangen, dass die 15 Minuten vorbei sind.

        Zugegeben gering, aber man sollte sich dessen vorher schon einmal bewußt sein.

        Stell dir folgenden Zeitablauf vor:

        10:00 Uhr: Er schreibt in mein Gästebuch
        10:01 Uhr: Er betrachtet seinen schönen Eintrag
        10:02 Uhr: Er betrachtet ihn immer noch
        10:05 Uhr: Er verlässt meine Homepage
        10:06 Uhr: Jetzt hat er auch die anderen 20 Browserfenster geschlossen und legt auf, nahezu zeitgleich, nämlich um
        10:06 Uhr: Zweiter User wählt sich ein, bekommt die IP-Adresse des ersten Users
        10:07 Uhr: Ganz zufällig gibt auch diese Uhser die URL zu meiner HP ein
        10:08 Uhr: Er studiert meine HP
        10:10 Uhr: Er liest Gästebucheinträge
        10:11 Uhr: Er möchte auch einen GB-Eintrag schreiben :S *arg*

        Das wird anscheinend ein Gästebuch für eine lokale NOMAAM-Gruppe;-)

        Ansonsten "mehrere Besucher mit der gleichen IP"!? Das ginge nur noch über ein Firmennetzwerk. Na ich will mal nicht hoffen, dass 300 Mitarbeiter zeitgleich alle in mein Gästebuch schreiben. *lol* Hilfä!!!

        Nicht nur Firmen benutzen HTTP-Proxies, auch diverse Anbieter von Internetzugängen (auch größere). Auch hier gehts hauptsächlich nur darum, es zu wissen.

        Grüße
          Klaus