PHP: Grosse Datei zeilenweise behandeln
Jörg
- php
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
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
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:
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.
Sorry, habe ich verwechselt.
Wenn set_time_limit() aufgerufen wird, dann startet der Zähler neu.
Linuchs
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
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
Wie mache ich das denn bei meiner grossen datei?
Hi,
Wie mache ich das denn bei meiner grossen datei?
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
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)