Jörg: PHP: Grosse Datei zeilenweise behandeln

Hallo Forum,

dieses Script läuft bei mir bei einer Datei von ca. 25000 Zeilen in den Script-Timeout.

<?php
$file = '2021.csv';
$file_handle = fopen($file, 'r');
while (!feof($file_handle)) {
 
  $zeile = fgets($file_handle);


if ( strpos($zeile,'|') === false ) {
// todo
} else {
  echo $zeile."<br>";
}
}
fclose($file_handle);

Wie mache ich das denn bei meiner grossen datei?

Jörg

  1. Wie mache ich das denn bei meiner grossen datei?

    Hat sich gerade rausgestellt, dass meine Datei defekt war und das Script deshalb nicht funktionierte. Nun läuft es auch über die 25000 Zeilen schnell und sauber durch.

    btw: Die Datei ist trotz der Endung csv (für die kann ich nichts) keine csv-Datei. Das nur zur Erklärung, warum ich nicht 'fgetcsv' nutze.

    Jörg

  2. Wie mache ich das denn bei meiner grossen datei?

    Ist ja nicht mehr nötig. Aber ich hatte mal eine komplizierte Optimierungsaufgabe. Man kann das Script-Timeout (auch mehrfach) verlängern:

    max_execution_time

    Ich weiß nicht mehr, ob die Zeit bei diesem Kommando neu startet (also immer wieder 30 sec) oder ob man die Sekundenzahl erhöhen muss.

    1. Sorry, habe ich verwechselt.

      Wenn set_time_limit() aufgerufen wird, dann startet der Zähler neu.

      Linuchs

    2. Hallo Linuchss,

      beachte aber auch, dass PHP nicht den einzigen Watchdog-Timer an der Kette hat:

      Ihr Webbserver kann andere Timeout-Einstellungen haben die ebenfalls die PHP-Ausführung unterbrechen können. Apache verfügt über eine TimeOut-Direktive und IIS hat eine CGI timeout Funktion. Beide sind als Standardwert auf 300 Sekunden eingestellt. Genauere Informationen finden Sie in der Dokumentation Ihres Webservers.

      Dazu kommt noch ein Timeout im Browser.

      Aber echte Langläufer lässt man auch nicht im Web laufen, sondern auf der Konsole. Die Doku schreibt dann:

      Wird PHP von der Kommandozeile ausgeführt so ist der Vorgabewert 0.

      Sprich: Es gibt dann kein Timeout. Eine Konsole hat man immer, und wenn der Webhoster einem keine erlaubt, dann eben mit einer PHP Installation auf dem eigenen Win/Lin/Mac-PC.

      Wenn man es aus irgendwelchen ominösen Gründen unbedingt auf dem Webserver tun muss, und vom Browser aus antriggern, und es einfach nicht schneller geht, dann muss man versuchen, die Aufgabe sinnvoll zu teilen.

      Zum Beispiel könnte man vor jedem fgets prüfen, wie lange das Script schon läuft. Kommt man in gefährliche Bereiche (deren Wert man im Zweifelsfall vom Browser per Query-Parameter mitgibt), merkt man sich mit ftell die aktuelle Position in der Eingabedatei, speichert die Arbeitsdaten in einer Datei (die hoffentlich nicht zu umfangreich sind) und meldet dem Browser zurück: teilweise fertig, ich war bei Position 47110815. Ein kleines JavaScript im Browser schickt dann den nächsten Request los, mit Parameter "mach bei Position 47110815 weiter" und das Script liest den Arbeitsstand wieder ein, positioniert die Eingabedatei mit fseek auf diese Position und verarbeitet den nächsten Block.

      Eine Alternative wäre, die Eingabedatei zu teilen, sofern das fachlich möglich ist. Zum Beispiel mit einem Aufteilscript in 100000 Zeilenblöcke, und danach ruft man pro Block das Verarbeitungsscript auf.

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Eine Alternative wäre, die Eingabedatei zu teilen, sofern das fachlich möglich ist. Zum Beispiel mit einem Aufteilscript in 100000 Zeilenblöcke

        Wer ein leistungsfähiges System hat kann das Schritt für Schritt probieren (Bitte erst alles lesen und über die Hardware und den freienSpeicherplatz nachdenken):

        So kann man eine Datei mit 100 Millionen Zeilen erzeugen :

        for i in {1..100000000}; do echo $i >> /tmp/zeilen; done
        

        ... welche auch eine „attraktive Größe“ hat:

        ls -lh /tmp/zeilen
        -rw-rw-r-- 1 fastix fastix 848M Feb 10 10:58 /tmp/zeilen
        

        Zerlegen:

        Man erzeuge ein temporäres Verzeichnis und wechsle geich hinein:

        cd $(mktemp -d)
        # eg. /tmp/tmp.JEmaiJh5UX$
        

        ... und zerlege die Datei in Stücke a 1000 Zeilen, welche als part.NNNNNNNN (-d -a 8) gespeichert werden:

        time split -l 1000 -da 8 /tmp/zeilen part.
        

        Das geht auf schnellen Geräten (Partitionen!) auch schnell:

        real	0m5,870s
        user	0m0,686s
        sys	0m4,837s
        

        vom Inhalt der einzelnen Dateien kann man sich wie folgt überzeugen:

        less part.00000000
        ...
        less part.00099999
        

        Auf weniger leistungsfähigen Systemen nehme man eine kleinere Datei mit z.B. nur 1 Mio Zeilen. (erster Schritt)

        Hint:

        Um die beachtliche Anzahl von Dateien loszuwerden lösche man entweder das Verzeichnis:

        rm -r /tmp/tmp.JEmaiJh5UX
        

        oder die Dateien in einer Schleife:

        for f in *; do rm $f; done
        

        letzteres dauert deutlich länger.

        Nicht vergessen, die Datei zu löschen:

        rm /tmp/zeilen
        

        Handbuch:

        man split oder Ubuntuusers.de->split

  3. Wie mache ich das denn bei meiner grossen datei?

    Mit fgets( $handle, $maxChars ) Zeile für Zeile lesen.

    1. Hi,

      Wie mache ich das denn bei meiner grossen datei?

      Mit fgets( $handle, $maxChars ) Zeile für Zeile lesen.

      Die Angabe der maximalen Anzahl der Zeichen verhindert den Timeout des Scripts?

      fgets wird ja schon verwendet, bisher nur ohne Zeilenlängenbegrenzung.

      M.E. verschlimmert das eher die Situation, weil damit noch zusätzliche Logik gebraucht wird, um Zeilen, die wegen der Begrenzung jetzt in mehreren Schritten gelesen werden, wieder zusammenzusetzen, bevor sie weiterverarbeitet werden.

      cu,
      Andreas a/k/a MudGuard

      1. Die Angabe der maximalen Anzahl der Zeichen verhindert den Timeout des Scripts?

        fgets wird ja schon verwendet, bisher nur ohne Zeilenlängenbegrenzung.

        fgets verwendet bei Nichtangabe die Größe der Datei als Zeilenlängenbegrenzung (siehe Handbuch)

         fgets ( resource $handle , int $length = ? ) : string
        

        M.E. verschlimmert das eher die Situation, weil damit noch zusätzliche Logik gebraucht wird, um Zeilen, die wegen der Begrenzung jetzt in mehreren Schritten gelesen werden, wieder zusammenzusetzen, bevor sie weiterverarbeitet werden.

        Die Längenangabe verschlimmert also nichts… intern dürfte ohnehin Zeichen für Zeichen gelesen werden... (siehe also fgets in C, fgetc in C)