Navigation
 Startseite
 Fachbücher
 Anzeigenmarkt
 Forum
 Webmaster News
 Script Newsletter
 Push Meldungen
 Kontakt
 Script Installation
 Php
 Php Tutorials
 Lernpfade
 Webhoster Vergleich
 Impressum

Community-Bereich
 kostenlos Registrieren
 Anmelden
 Benutzerliste

Script Datenbank
 Script Archiv
 Script Top 20
 Screenshots
 Testberichte

Suche
 

Unsere Php Scripts
 Counter Script
 Umfrage Script
 Bilder Upload Script
 Terminverwaltung
 Simple PHP Forum
 RSS Grabber

Tools und Generatoren
 .htpasswd Generator
 md5 Generator
 base64 Generator
 Markdown to HTML
 Colorpicker
 Unix timestamp Tool
 Unit Test Generator
 TLD Liste
 Webkatalog‑Verzeichnis

Hosterplus.de
Bekommen Sie Speicherplatz (Webspace), Domains...
https://www.Hosterplus.de
Webspace-Mieten.d...
Webhosting mit Plesk aus Erfurt: Vier...
https://www.Webspace-Mieten.de
 
 
 

PHP fseek() Function: Dateizeiger gezielt setzen

Sie befinden sich: Home > Php Tutorial > PHP fseek() Function: Datei...

PHP fseek() Function: Dateizeiger gezielt setzen
Veröffentlicht am:
26.10.2026
Hits / Besucher:
5
Sprache:
  Deutsch
Tutorial Art:
eigenes
Eingetragen von:
Merkliste:
 
Beschreibung

Die Admin-Seite soll die letzten fünfzig Zeilen der Logdatei zeigen, und die Datei ist inzwischen mehrere hundert Megabyte groß. Mit file() lädt PHP jede einzelne Zeile in den Speicher, um am Ende fast alle wieder wegzuwerfen. Schneller geht es, wenn das Skript direkt ans Ende springt. Wie man eine Datei öffnet, liest und schreibt, zeigt der Artikel Dateihandling mit PHP. Der Dateizeiger selbst kommt dort nur am Rand vor, obwohl an ihm hängt, ob PHP fseek() ans Ende springen kann und wann das scheitert.

Illustration zum Tutorial: PHP fseek() Function: Dateizeiger gezielt setzen

Die Beispiele liefen unter Windows und Linux mit PHP 8.4. Meist verhalten sich beide gleich, beim Textmodus nicht.

Was PHP fseek() mit dem Dateizeiger macht

Jeder geöffnete Stream merkt sich eine Position, den Dateizeiger (englisch file pointer). Lesen und Schreiben beginnen dort und schieben ihn um die verarbeiteten Bytes weiter. Gezählt wird ab 0 und in Bytes, nicht in Zeichen. Ein Umlaut in UTF-8 belegt also zwei Positionen. PHP fseek() setzt diese Position neu. Vorbild ist die gleichnamige Funktion aus der C-Standardbibliothek. So lässt sich ein Kopfbereich überspringen:

<?php

$fp = fopen(__DIR__ . '/export.dat', 'rb');

/* die ersten 512 Bytes sind der Kopf */
fseek($fp, 512);
$daten = fread($fp, 1024);

Ob ein Aufruf gelingt, hängt an zwei Fragen: Kann der Stream überhaupt springen, und landet das Ziel vor dem Dateianfang?

flowchart TD
    A[fseek aufrufen] --> B{Stream suchbar?}
    B -->|nein| C[Ergebnis -1 mit Warnung]
    B -->|ja| D{Bezugspunkt}
    D -->|SEEK_SET| E[Anfang plus Offset]
    D -->|SEEK_CUR| F[Position plus Offset]
    D -->|SEEK_END| G[Dateiende plus Offset]
    E --> H{Ziel kleiner 0?}
    F --> H
    G --> H
    H -->|ja| I[Ergebnis -1, Zeiger bleibt]
    H -->|nein| J[Ergebnis 0, Zeiger steht dort]

Die Signatur im Original: Parameter und Rückgabewert

Zum Zeiger gehören drei Funktionen:

fseek(

resource $stream,
int $offset,
int $whence = SEEK_SET
): int

ftell(resource $stream): int|false
rewind(resource $stream): bool

/* Sets the file position indicator for the file
referenced by stream. The new position, measured
in bytes from the beginning of the file, is
obtained by adding offset to the position
specified by whence.

offset - the number of bytes; a negative value
is allowed with SEEK_CUR and SEEK_END
whence - SEEK_SET: from the beginning of the file
SEEK_CUR: from the current position
SEEK_END: from the end of the file
return - the function returns 0 on success, -1
on failure. Seeking past the end of
the file is allowed; if data is then
written, the gap reads as bytes with
value 0.

In append mode (a, a+) data is always written
at the end of the file. Not all streams support
seeking: forward seeking is done by reading and
discarding data, other forms of seeking fail. */

Der Rückgabetyp von PHP fseek() ist int, nicht bool. ftell() liefert die aktuelle Position oder false, rewind() dagegen true oder false. Drei Funktionen, drei verschiedene Arten, Erfolg zu melden.

SEEK_SET, SEEK_CUR und SEEK_END

Der dritte Parameter legt fest, von wo aus der Offset zählt. An einer Datei mit dem Inhalt Hallo Welt, zehn Bytes lang, ergaben sich unter Windows und Linux dieselben Werte:

<?php

file_put_contents('gruss.txt', 'Hallo Welt');
$fp = fopen('gruss.txt', 'r+b');

fseek($fp, 6);
echo fread($fp, 3);
/* Wel, der Zeiger steht danach bei 9 */

fseek($fp, -2, SEEK_CUR);
echo ftell($fp);
/* 7 */

fseek($fp, -4, SEEK_END);
echo fread($fp, 10);
/* Welt */
Bezugspunkt Neue Position Negativer Offset
SEEK_SET genau der Offset, gezählt ab dem Dateianfang immer -1, der Zeiger bleibt stehen
SEEK_CUR aktuelle Position plus Offset erlaubt, solange das Ziel nicht vor 0 liegt
SEEK_END Dateigröße plus Offset der Normalfall, um vom Ende aus zu lesen

Die Größe einer Datei lässt sich zwar mit SEEK_END und ftell() ermitteln. Direkter sind filesize() für einen Pfad und fstat() für einen offenen Stream.

Warum PHP fseek() 0 und -1 liefert

Weil 0 Erfolg bedeutet, führen gleich zwei naheliegende Prüfungen in die Irre. if (fseek($fp, $pos) === false) trifft nie zu, und if (!fseek($fp, $pos)) ist ausgerechnet bei Erfolg wahr. Richtig ist der Vergleich mit 0:

<?php

if (fseek($fp, $pos) !== 0) {
throw new RuntimeException(
"Sprung auf Byte $pos misslungen"
);
}

Die Fehlerfälle verhielten sich im Test leiser als erwartet. Ein Ziel vor dem Dateianfang, etwa fseek($fp, -1) oder fseek($fp, -20, SEEK_END) bei zehn Bytes, lieferte -1 ohne jede Warnung, und der Zeiger blieb, wo er war. Ein unbekannter Bezugspunkt wie 5 ergab ebenfalls still -1. Laut wurde PHP fseek() erst bei falschen Typen: Ein String als Offset warf TypeError: fseek(): Argument #2 ($offset) must be of type int, string given, ein geschlossenes Handle TypeError: fseek(): supplied resource is not a valid stream resource. Ein Float wie 2.5 wurde mit einer Deprecated-Meldung auf 2 abgeschnitten.

Ein erfolgreicher Sprung hat außerdem einen Nebeneffekt: Nach dem Lesen bis zum Ende meldet feof() true, und der Sprung setzt diese Markierung zurück. Nach fseek($fp, 0) stand feof() wieder auf false, ein zweiter Durchlauf funktioniert also ohne neues Öffnen.

ftell() und rewind(): Position lesen und zurücksetzen

ftell() verrät, wo der Zeiger gerade steht. Nach jedem fgets() zeigt er auf das erste Byte hinter dem Zeilenumbruch. Bei der Datei erste, zweite, dritte mit gemischten Zeilenenden waren das 6 und 14. Wer sich diese Werte merkt, kann jede Zeile später mit PHP fseek() direkt wieder anspringen. rewind($fp) ist die Kurzform von fseek($fp, 0) und liefert true. Bei einem Stream, der nicht springen kann, kam false mit der Warnung rewind(): Stream does not support seeking.

Unter Windows gibt es dabei eine Falle, die nur im Textmodus auftritt. Wer eine Datei mit 'rt' öffnet, bekommt Zeilen ohne das Wagenrücklaufzeichen, und ftell() zählt dann nicht mehr die Bytes in der Datei:

<?php

$inhalt = "eins\r\nzwei\r\ndrei\r\n";
file_put_contents('liste.txt', $inhalt);

/* t: Textmodus */
$fp = fopen('liste.txt', 'rt');
fgets($fp);
$pos = ftell($fp);
/* Windows 5, Linux 6 */

fseek($fp, $pos);
var_dump(fgets($fp));
/* Windows "\n", Linux "zwei\r\n" */

Unter Windows meldete ftell() vor den drei Zeilen 0, 5 und 10, die echten Byte-Positionen sind 0, 6 und 12. Der Sprung auf 5 landete deshalb zwischen Wagenrücklauf und Zeilenumbruch, und fgets() lieferte eine leere Zeile. Unter Linux hat t keine Wirkung. Wer Positionen speichert oder anspringt, öffnet Dateien deshalb immer mit b, also 'rb', 'r+b' oder 'c+b'.

Hinter das Dateiende und im Modus a

Ein Sprung über das Ende hinaus ist für PHP fseek() kein Fehler. Bei der Datei mit zehn Bytes lieferte fseek($fp, 100) eine 0, ftell() meldete 100, fread() einen leeren String und feof() true. Erst ein fwrite() von einem Byte an dieser Stelle verändert die Datei: Sie war danach 101 Bytes groß, und die Lücke dazwischen bestand aus Nullbytes. Was das für den Platz auf der Festplatte bedeutet, erklärt das Tutorial zu ftruncate().

Im Anhängemodus, typisch für Logdateien, meldet PHP fseek() Erfolg und bewirkt beim Schreiben trotzdem nichts:

<?php

file_put_contents('log.txt', 'ABCDEFGHIJ');

$fp = fopen('log.txt', 'a+');
fseek($fp, 2);
echo fread($fp, 3);
/* CDE, das Lesen folgt dem Zeiger */

fwrite($fp, 'xy');
echo ftell($fp);
/* 7, tatsächlich steht er bei 12 */
fclose($fp);

echo file_get_contents('log.txt');
/* ABCDEFGHIJxy, angehängt statt eingefügt */

Im Modus a meldete ftell() direkt nach dem Öffnen sogar 0, obwohl die Datei zehn Bytes hatte. Auf die Position ist dort also kein Verlass. Mit 'r+' oder 'c+' landet der Text dagegen an der gewählten Stelle. Aus ABCDEFGHIJ wurde dabei ABxyEFGHIJ: Die beiden Buchstaben ersetzen, was dort stand. Einfügen kann kein Modus. Wer einen neuen Eintrag an den Anfang eines Gästebuchs setzen will, schreibt Eintrag und alten Inhalt in eine temporäre Datei und benennt sie danach um.

Nicht jeder Stream ist suchbar

PHP fseek() funktioniert mit Dateien, aber nicht mit jedem Stream. Ob ein Stream springen kann, verrät stream_get_meta_data($h)['seekable']. Gemessen auf beiden Systemen:

Stream suchbar Sprung an den Anfang
Datei, php://memory, php://temp, tmpfile() ja 0
php://stdin aus einer Pipe nein -1 mit Warnung
php://stdin aus einer umgeleiteten Datei ja 0
php://output, HTTP-Stream nein -1 mit Warnung
compress.zlib:// ja 0, aber kein SEEK_END

Die Warnung lautet fseek(): Stream does not support seeking. Beim gepackten Stream hieß es SEEK_END is not supported. Beim HTTP-Stream gelang ein Sprung nach vorn auf Byte 20 zwar, aber nur, weil diese Bytes schon im Lesepuffer standen. Bei einer Datei mit 1,8 MB scheiterte fseek($h, 100000) mit -1 und derselben Warnung. Verlässlich vorwärts ging es nur relativ zur aktuellen Stelle: fseek($h, 100000, SEEK_CUR) lieferte 0 und die richtigen Bytes, denn dabei liest PHP und verwirft, was dazwischen liegt. So steht es auch im Handbuch. Zurück ging es nie.

Darauf bauen sollte man nicht. Wer einen fremden Stream mehrfach lesen muss, kopiert ihn in einen temporären Stream, der immer springen kann. Derselbe Weg hilft, wenn ein fseek() für Strings gesucht ist: Auch ein Text in einer Variablen wird so zum suchbaren Stream:

<?php

$h = fopen('https://example.com/liste.csv', 'rb');

if (!stream_get_meta_data($h)['seekable']) {
$kopie = fopen('php://temp', 'w+b');
stream_copy_to_stream($h, $kopie);
fclose($h);
$h = $kopie;
}
rewind($h);

/* ein String als suchbarer Stream */
$s = fopen('php://memory', 'w+b');
fwrite($s, $text);
fseek($s, 0);

Praxis: die letzten Zeilen einer großen Datei

Zurück zur Logdatei aus der Einleitung. Statt alles zu laden, springt die Funktion ans Ende und liest rückwärts in Blöcken von 4 KiB, bis genug Zeilenumbrüche beisammen sind:

<?php

function letzte_zeilen(string $pfad, int $n): array
{
$fp = fopen($pfad, 'rb');
fseek($fp, 0, SEEK_END);
$pos = ftell($fp);
$puffer = '';
while ($pos > 0
&& substr_count($puffer, "\n") <= $n) {
$lesen = min(4096, $pos);
$pos -= $lesen;
fseek($fp, $pos);
$puffer = fread($fp, $lesen) . $puffer;
}
fclose($fp);
$rest = rtrim($puffer, "\r\n");
if ($rest === '') {
return [];
}
$zeilen = preg_split('/\r?\n/', $rest);
return array_slice($zeilen, -$n);
}

Gemessen an einer Datei mit 500.000 Zeilen und 31 MiB, jeweils für die letzten zehn Zeilen:

Weg Windows Linux
file() und array_slice() 48,5 ms, rund 88 MiB 111,0 ms, rund 85 MiB
letzte_zeilen() 0,23 ms, rund 0,02 MiB 0,11 ms, rund 0,02 MiB

Beide Wege lieferten dieselben Zeilen. Eine leere Datei ergibt ein leeres Array, und Windows-Zeilenenden schneidet preg_split() mit ab. Bei einem memory_limit von 128 MiB fehlt file() nicht mehr viel bis zum Abbruch, während die Variante mit PHP fseek() von der Dateigröße unabhängig bleibt. Soll die Seite eine wachsende Datei fortlaufend mitlesen wie tail -f, zeigt das Tutorial zu fstat() das passende Muster.

Praxis: Zeilenindex und Import fortsetzen mit PHP fseek()

Eine Oberfläche blättert seitenweise durch ein großes Anrufprotokoll. Für Zeile 450.000 müsste das Skript jedes Mal alle Zeilen davor lesen. Ein Index aus den Positionen, einmal als Array aufgebaut, erspart das:

<?php

$fp = fopen('anrufe.log', 'rb');
$index = [];
while (true) {
$pos = ftell($fp);
if (fgets($fp) === false) {
break;
}
$index[] = $pos;
}

/* Zeile 450.000, der Index zählt ab 0 */
fseek($fp, $index[449999]);
echo fgets($fp);

Der Aufbau dauerte für 500.000 Zeilen 75,9 ms unter Windows und 41,5 ms unter Linux. Danach brauchte jeder Sprung mit PHP fseek() unter 0,04 ms, ohne Index waren es 53,9 und 25,8 ms je Zugriff. Der Index lohnt sich also schon ab dem zweiten Sprung. Er gehört in eine Datei oder einen Cache und muss neu entstehen, sobald sich die Protokolldatei ändert.

Nach demselben Prinzip setzt ein abgebrochener Import an der richtigen Stelle wieder ein. Das Skript merkt sich regelmäßig die Position hinter der zuletzt verarbeiteten Zeile, beim Neustart springt es dorthin:

<?php

$stand = __DIR__ . '/import.pos';
$fp = fopen(__DIR__ . '/lieferung.csv', 'rb');

$start = (int) @file_get_contents($stand);
fseek($fp, $start);

$n = 0;
while (($zeile = fgets($fp)) !== false) {
importiere($zeile);
if (++$n % 1000 === 0) {
file_put_contents($stand, ftell($fp));
}
}
file_put_contents($stand, ftell($fp));

Die Position nach jeder Zeile zu schreiben kostet: 10.000 Aufrufe von file_put_contents() brauchten im Test 2,3 Sekunden unter Windows und 11,5 im Linux-Container. Alle 1000 Zeilen genügt, wenn importiere() eine doppelt gelesene Zeile verträgt. Die gespeicherte Position gilt nur für genau diese Datei. Ersetzt der Lieferant sie in der Nacht, zeigt der Wert mitten in eine fremde Zeile. Eine Prüfung auf Größe und Änderungszeit gehört deshalb dazu.

Praxis: Binärdaten und Datensätze fester Länge

Viele Dateiformate legen Informationen an festen Stellen ab. Schon der Dateityp steht an einer solchen Stelle: Ein PNG beginnt mit acht festen Bytes, die sich mit PHP strncmp() gegen die Signatur prüfen lassen. In einer PNG-Datei stehen Breite und Höhe ab Byte 16 als zwei 32-Bit-Zahlen. PHP fseek() springt direkt dorthin, unpack() liest die Werte:

<?php

$fp = fopen(__DIR__ . '/bild.png', 'rb');
fseek($fp, 16);
$kopf = unpack('Nbreite/Nhoehe', fread($fp, 8));

echo $kopf['breite'], ' x ', $kopf['hoehe'];
/* 550 x 310 */

Im Test stimmte das Ergebnis mit getimagesize() überein. Dasselbe Prinzip trägt Dateien mit Datensätzen fester Länge. Satz Nummer $nr beginnt bei $nr * 20, und ändern lässt er sich an Ort und Stelle:

<?php

const LAENGE = 20;
$fp = fopen(__DIR__ . '/kunden.dat', 'c+b');
flock($fp, LOCK_EX);

/* zweiter Satz, gezählt ab 0 */
$nr = 1;
fseek($fp, $nr * LAENGE);
echo fread($fp, LAENGE);

fseek($fp, $nr * LAENGE);
$satz = str_pad('A002;Schulze-Wagner', LAENGE);
fwrite($fp, substr($satz, 0, LAENGE));
flock($fp, LOCK_UN);
fclose($fp);

Das str_pad() ist kein Schmuck. Ohne es überschrieb fwrite($fp, 'XYZ') im Test nur drei Bytes, und hinter dem neuen Text stand weiter der Rest des alten Satzes. Umgekehrt kürzt str_pad() nie: Ein Wert mit 33 Bytes überschrieb im Test den folgenden Satz. Das substr() schneidet deshalb auf die Satzlänge zu. Arbeiten mehrere Prozesse an derselben Datei, gehören Sprung und Schreiben unter eine gemeinsame Sperre, wie sie das Tutorial zu flock() beschreibt.

Große Dateien über 2 GB

In älteren Forenbeiträgen heißt es, PHP fseek() scheitere ab 2 GB. Das lag an 32-Bit-Ganzzahlen, in die keine größere Position passte. Auf beiden Testsystemen meldete PHP_INT_SIZE den Wert 8, die Positionen sind dort 64-Bit-Zahlen, und die Grenze spielt keine Rolle. Wer auf einem Altsystem PHP_INT_SIZE = 4 sieht, hat ein 32-Bit-PHP und die alte Grenze noch.

Fazit

PHP fseek() bewegt den Dateizeiger in Bytes, ab dem Anfang, ab der aktuellen Stelle oder ab dem Ende, und meldet 0 oder -1. Geprüft wird deshalb auf !== 0, nie auf false. Ein Ziel vor dem Anfang scheitert still, eines hinter dem Ende gelingt und hinterlässt beim Schreiben Nullbytes.

Drei Situationen verdienen Misstrauen, obwohl kein Fehler gemeldet wird: der Anhängemodus, in dem jedes Schreiben am Ende landet und ftell() falsche Werte liefert, der Textmodus unter Windows mit verschobenen Positionen und fremde Streams, bei denen vor dem Sprung ein Blick auf seekable gehört. Mit Dateien im Binärmodus trägt PHP fseek() dann genau die Muster, die bei großen Dateien zählen: die letzten Zeilen ohne Volllast, einen Index für beliebige Zeilen und Datensätze, die sich an Ort und Stelle ändern lassen.

 

Tags:

 

 

Kommentare (0)

Noch keine Kommentare. Sei der Erste!

Melde dich an, um einen Kommentar zu schreiben.
Bücherregal mit drei Büchern: 'PHP 4 - Grundlagen und Profiwissen' von Hanser Verlag, 'Webdesign in a Nutshell' von O'Reilly Verlag, und 'Webgestaltung' von Galileo Computing.