14.3    Dateien öffnen

Wollen Sie eine Datei bearbeiten, müssen Sie diese zunächst öffnen. Wie im vorangehenden Abschnitt erwähnt, stehen Ihnen hierzu drei Funktionen zur Verfügung, die alle einen Zeiger auf ein FILE-Objekt zurückliefern. Hierzu die einzelnen Funktionen im Überblick:

#include <stdio.h>
FILE *fopen( const char * restrict filename,
const char * restrict mode );

Mit der Funktion fopen() öffnen Sie eine Datei mit dem Namen filename. filename darf auch eine Pfadangabe sein. Wenn eine Datei nicht geöffnet werden konnte oder nicht existiert, gibt die Funktion den NULL-Zeiger zurück. Mit dem zweiten Argument mode bestimmen Sie den Zugriffsmodus. Bei der Festlegung des Zugriffsmodus ist nur ein String mit einem bestimmten Inhalt erlaubt. Die einzelnen Zugriffsmodi und ihre Bedeutung sind in Tabelle 14.1 aufgelistet.

Modus

Bedeutung

"r"

Öffnet eine Datei zum Lesen (r = read).

"w"

Öffnet eine Datei zum Schreiben. Existiert diese Datei nicht, wird sie neu erzeugt. Existiert die Datei mit Inhalt, wird dieser auf 0 gekürzt und ist somit verloren (w = write).

"a"

Wie der Modus "w", nur wird hierbei der Inhalt einer eventuell existierenden Datei nicht gelöscht, sondern der neu zu schreibende Inhalt wird am Dateiende angefügt (aappend).

"r+"

Öffnet eine Datei zum Lesen und Schreiben. Existiert diese Datei nicht, wird der NULL-Zeiger zurückgegeben.

"w+"

Wie mit "r+" wird eine Datei zum Lesen und Schreiben geöffnet. Es wird gegebenenfalls eine neue Datei angelegt, wenn sie nicht existiert oder der alte Inhalt der ursprünglichen Datei gelöscht wird.

"a+"

Hiermit wird eine Datei zum Lesen und Schreiben am Ende der Datei geöffnet. Ist die Datei noch nicht vorhanden, wird sie neu angelegt.

Tabelle 14.1    Modus zum Öffnen einer Datei mit fopen()

Mithilfe des +-Symbols können Sie immer eine Datei zum Lesen und Schreiben öffnen.

Allerdings muss beim Wechseln vom Schreib- zum Lesezugriff die Schreiboperation mit der Funktion fflush(), fsetpos(), fseek() oder rewind() abgeschlossen werden, wenn Sie unmittelbar darauf eine Leseoperation ausführen wollen. Wollen Sie nach einer Leseoperation eine Schreiboperation aufrufen, müssen Sie eine der Funktionen fseek(), fsetpos() oder rewind() zur richtigen Positionierung des Schreibzeigers aufrufen, es sei denn, es wurde das Dateiende (EOF) gelesen.

Verwenden von Zugriffsrechten

Damit Sie überhaupt eine Datei in einem bestimmten Modus öffnen können, müssen die entsprechenden Zugriffsrechte vorhanden sein. Sind die Rechte für normale Benutzer bei einer Datei nur auf Lesen eingestellt, schlägt der Versuch, die Datei zum Schreiben zu öffnen, fehl. In diesem Fall wird ein NULL-Zeiger zurückgegeben. Unter Windows werden diese Zugriffsrechte allerdings weniger streng als auf Linux-/Unix-Systemen behandelt.

An alle Modi können Sie noch das Zeichen b anfügen (beispielsweise "rb" oder "w+b"). Damit wird eine Datei im Binärmodus geöffnet, d. h., sie wird mit einem binären Stream verbunden. Ohne das zusätzliche Zeichen b werden alle Dateien als Textdatei, also im Textmodus geöffnet.

Hier ein einfaches Beispiel zur Funktion fopen():

00  // Kapitel14/fopen_beispiel.c
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <string.h>
04 #define NAME 1024

05 int main(void) {
06 char filename[NAME];
07 printf("Welche Datei soll geoeffnet werden: ");
08 if( fgets(filename, sizeof(filename), stdin) == NULL ) {
09 fprintf(stderr, "Fehler bei der Eingabe\n");
10 return EXIT_FAILURE;
11 }
12 // Newline entfernen
13 size_t p = strlen(filename);
14 filename[p-1] = '\0';
15 FILE *fp = fopen(filename, "r");
16 if( fp != NULL ) {
17 printf("Datei zum Lesen geoeffnet\n");
18 fclose(fp);
19 }
20 else {
21 fprintf(stderr,"Datei konnte nicht geoeffnet werden");
22 }
23 return EXIT_SUCCESS;
24 }

Listing 14.1    In dem letzten Listing wird eine Datei mit fopen() zum Lesen geöffnet.

Bei diesem Beispiel sollen Sie eine Datei zum Lesen (gerne auch mit Pfad) eingeben. In Zeile (15) wird versucht, diese Datei zum Lesen zu öffnen. Ob das klappt oder nicht, wird in Zeile (16) überprüft: Ist der Rückgabewert von fopen() ungleich NULL? Ist dies der Fall, wird eine entsprechende Erfolgsmeldung ausgegeben, und die Datei wird in Zeile (18) mit fclose() gleich wieder geschlossen.

Dateipfade unter Windows

Unter Windows werden Pfadangaben durch einen Backslash voneinander getrennt. Dies hat direkte Auswirkungen auf fopen(), da hier in dem Parameter filename der Backslash als Metazeichen, also durch einen doppelten Backslash, angegeben werden muss. Wollen Sie beispielsweise die Datei User\Documents\Texte\test.txt unter Windows 10 mit fopen() öffnen, müssen Sie wie folgt vorgehen:

FILE *Datei=fopen("User\\Documents\\Texte\\text.txt","r");

14.3.1    Exklusiver Dateizugriff

Mit dem C11-Standard wurde zur Funktion fopen() ein exklusiver Dateizugriff hinzugefügt, mit dem geprüft wird, ob eine Datei nicht vorhanden ist, und mit dem anschließend die Datei erstellt und geöffnet wird. Sollte eine Datei vorhanden sein, schlägt der Funktionsaufruf von fopen() fehl. Bisher musste dies mit einem zweimaligen Aufruf von fopen() gemacht werden, indem erst mit dem Modus "r" getestet wurde, ob die Datei vorhanden ist, um dann eventuell mit dem Modus "w" diese Datei zu erzeugen. Da es sich hierbei um zwei Aktionen handelt, wobei zwischen dem ersten und zweiten fopen()-Aufruf ein Zeitfenster für Angreifer vorhanden ist, in dem diese eine eigene Datei mit demselben Namen erzeugen könnten, war diese Möglichkeit immer schon etwas unsicher.

Alternativen waren dabei die Möglichkeiten des POSIX-Standards, die Flags O_EXCL und O_CREATE zu kombinieren und die Funktion open() aufzurufen, oder den exklusiven Modus mit x, den manche C-Standardbibliotheken hierzu schon anboten. Dieser wurde mit C11 standardisiert und kann daher für den exklusiven Dateizugriff als atomare Aktion aufgerufen werden. Somit können Sie mit fopen("datei.txt", "wx") eine Datei erzeugen und haben auch einen exklusiven Zugriff zum Schreiben auf diese Datei. Auch der Modus "w+x" kann hierbei verwendet werden, womit Sie dasselbe erreichen, nur dass neben dem Schreibzugriff auch der Lesezugriff exklusiv ist. Zusätzlich können Sie in beiden Modi das b anhängen, um die Dateien im binären Modus zu öffnen (beispielsweise: "wbx" oder "wb+x").

14.3.2    Weitere Dateifunktionen

Jetzt kommen wir zu den anderen beiden Funktionen, mit denen Sie eine Datei öffnen können. Zunächst folgt die Funktion freopen():

#include <stdio.h>
FILE *freopen( const char * restrict filename,
const char * restrict mode,
FILE * restrict stream );

Die Funktion freopen() öffnet, wie auch fopen(), die Datei filename im Modus mode. Im Gegensatz zu fopen() wird allerdings kein neuer Stream erzeugt, sondern es wird der Stream verwendet, den Sie mit dem dritten Argument stream angeben. freopen() wird gerne genutzt, um einen bereits geöffneten Stream zu schließen und in einem neuen Modus wieder zu öffnen. (Bedenken Sie auch hier wieder, dass der Stream der Teil ist, der an eine Datei gebunden wird und der die Funktionen ausführt.)

Eine weitere Anwendung von freopen() ist es, die Standard-Streams stdin, stdout und stderr umzulenken. Hierzu folgt jetzt ein einfaches Beispiel:

00  // Kapitel14/freopen_beispiel.c
01 #include <stdio.h>
02 #include <stdlib.h>

03 int main(void) {
04 printf("Diesen Text koennen Sie sehen\n");
05 if( freopen("logfile.txt", "w", stdout) == NULL ) {
06 fprintf(stderr, "Fehler bei freopen\n");
07 return EXIT_FAILURE;
08 }
09 printf("Dieser Text steht in logfile.txt\n");
10 return EXIT_SUCCESS;
11 }

In Zeile (04) wird die printf()-Ausgabe noch, wie gewöhnlich, über die Standardausgabe gemacht. Mit Zeile (05) lenken Sie über freopen() die Standardausgabe (stdout) in die Datei logfile.txt um. Daher wird der Text in Zeile (09) nicht mehr auf den Bildschirm, sondern in die Datei logfile.txt geschrieben. Natürlich können Sie hier unter Linux auch Gerätedateien angeben, z. B. /dev/tty1. Hiermit können Sie z. B. die Ausgaben Ihres Raspberry Pi an einen PC weiterleiten, der über eine serielle Schnittstelle mit diesem verbunden ist. Beachten Sie in diesem Fall jedoch, dass eine solche Umleitung nur gilt, solange Ihr Programm läuft.

Die letzte Funktion zum Öffnen einer Datei ist tmpfile(). Hier ist die Syntax dazu:

#include <stdio.h>
FILE *tmpfile(void);

Mit dieser Funktion wird eine neue temporäre Datei mit einem eindeutigen Namen erzeugt. Die Datei wird binär mit dem Modus "wb+" zum Lesen und Schreiben geöffnet. Die temporäre Datei wird nach dem Schließen mittels fclose() oder mit Beendigung des Programms automatisch wieder gelöscht. Konnte keine temporäre Datei geöffnet werden, wird der NULL-Zeiger zurückgegeben. Wenn Sie das Programm abnormal beendet haben, ist es implementierungsabhängig, ob die temporäre Datei gelöscht wird oder nicht. Auch die Anzahl der maximal gleichzeitig geöffneten temporären Dateien ist mit TMP_MAX in der Header-Datei stdio.h beschränkt.