Ein- und Ausgabe in Dateien

C-Programme beziehen ihre Daten aus unterschiedliche Datenströmen, z.B. von der Konsole, aus Dateien, oder aus Zeichenketten. Ein Datenstrom (stream) ist für ein Programm Quelle oder Ziel von Daten und besteht aus einer eine Folge von Zeilen; jede Zeile enthält null oder mehr Zeichen und ist mit '\n' abgeschlossen. Das Lesen und Schreiben von Daten in Datenströme geschieht mittels des Dateizeigers FILE *fp und verschiedener Funktionen der C-Standardbibliothek stdio.h:
fopen, fclose – Datei öffnen und schließen
fgets – Daten zeilenweise aus einem Stream (Konsole oder Datei) lesen,
fgetc, fputc – Daten zeichenweise aus einem Stream (Konsole oder Datei) lesen /schreiben,
fread, fwrite – Daten blockweise aus einem Stream (Konsole oder Datei) lesen / schreiben,
fprintf, fscanf – tabellenartig formatierte Daten in Datei schreiben bzw. aus Datei lesen,
fseek, feof, ferror - diverse Hilfsfunktionen zum Positionieren des Dateizeigers, für Fehler etc.

Es hängt von der Art der Daten ab, welche Funktionen zu verwenden sind. Bei strukturierten tabellenartigen Daten verwendet man die formatierte Eingabe mit scanf / fscanf / sscanf, um Texte zeilenweise einzulesen, fgets, um Daten blockweise einzulesen, fread. Um Texte zeichenweise einzulesen, verwendet man getchar / fgetc / fputc.

1 Datei öffnen / schließen

Der Zugriff auf eine Datei geschieht mit Hilfe des Zeigers FILE *fp und der Funktionen fopen (Datei öffnen) und fclose (Datei schließen). Die Funktion fopen öffnet eine Datei und erhält als Parameter den Dateizeiger fp, den Dateinamen und einen Zugriffsmodus ("r" – read, "a" – append, "w" – write). Die Funktion fclose() schließt eine geöffnete Datei. Es ist wichtig, eine geöffnete Datei auch wieder zu schließen, da ansonsten ggf. andere Programme nicht darauf zugreifen können.

Beispiel: Datei öffnen und schließen
Verwendete Funktionen: fopen, fclose

In diesem Beispiel öffnen wir eine Datei daten.txt im schreibenden Modus, schreiben einen kurzen Text in die Datei und schließen sie wieder.

  • Zeile 4: Dateizeiger anlegen: FILE *fp;
  • Zeile 5: Datei öffnen: fopen_s(&fp, filename, "w"); öffnet den Datenstrom fp
  • Zeile 6 - 9: Prüfen, ob Datei geöffnet werden konnte.
  • Zeile 14: Datei schließen: fclose(fp); schließt den Datenstrom fp.
#include <stdio.h>
int main(void) {
    char* filename = "daten.txt";
    FILE* fp = NULL; // (1)
    fopen_s(&fp, filename, "w"); // (2)
    if (fp == NULL) { // (3)
        printf("Fehler!\n");
        exit(1);
    }
    printf("Datei wurde geoeffnet!\n"); // Schreibe auf Konsole
    fprintf(fp, "Hallo zusammen!");   // Schreibe in Datei
    fprintf(fp, "Geht doch!");   // Schreibe in Datei
    fclose(fp); // (4)
}
Die Funktion fopen

Bei der Verwendung der Funktion fopen müssen der Dateiname und der Zugriffsmodus korrekt angegeben werden.

Dateinamen korrekt angeben

Bei Dateinamen können entweder absolute oder relative Dateipfade angegeben und es müssen Besonderheiten des Betriebssystems beachtet werden. Beispiel Windows: Windows unterscheidet bei Dateinamen nicht zwischen Groß- und Kleinschreibung, Linux schon. Ein absoluter Dateipfad wird angegeben, indem man für jede Ordnerebene zwei \\ (backslash)-Zeichen angibt. Wenn unter Windows kein Dateipfad angegeben wird, sucht das System die Datei in dem Verzeichnis, wo sich die ausführbare .exe-Datei befindet.

Zugriffsmodus korrekt angeben

Die Funktion fopen hat für das Öffnen einer Datei drei Zugriffsmodi.

  • "w" – schreibender Zugriff: Falls eine Datei mit dem angegebenen Namen nicht existiert, wird sie neu angelegt. Falls die Datei schon existiert, wird der Inhalt gelöscht und sie wird wie eine neue Datei behandelt.
  • "a" – anfügender Zugriff: Falls eine Datei mit dem angegebenen Namen nicht existiert, wird sie neu angelegt. Falls eine Datei mit dem angegebenen Namen existiert, wird der neue Inhalt am Ende der Datei angefügt.
  • "r" – lesender Zugriff: Falls eine Datei mit dem angegebenen Namen nicht existiert, wird NULL zurückgegeben. Falls eine Datei mit dem angegebenen Namen existiert, wird ein Zeiger fp auf die Datei zurückgegeben und der Inhalt kann gelesen werden.

2 Datei zeilenweise lesen

Textdateien mit Fließtext-Inhalt enthalten unstrukturierte Daten mit Zeilen, die unterschiedlich lang und unterschiedlich aufgebaut sind. In diesem Fall wird der Text zeilenweise mit fgets ausgelesen. Die Funktion char* fgets ( char * str, int num, FILE * stream ) liest num Zeichen aus dem Datenstrom stream in eine Zeichenkatte str. Bei Erfolg gibt fgets als Rückgabewert die Zeichenkette str zurück, die den eingelesenen Text enthält. Wenn beim Versuch, ein Zeichen zu lesen, das Dateiende erreicht wird, wird der EOF-Indikator gesetzt (feof). Wenn ein Lesefehler auftritt, wird der Fehlerindikator (ferror) gesetzt und ein Nullzeiger zurückgegeben.

Beispiel: Textdatei zeilenweise einlesen

In diesem Beispiel wird eine Textdatei myfile.txt mit unterschiedlich langen Zeilen zeilenweise in eine string-Variable zeile eingelesen, die Zeile selber und die Länge der Zeile werden ausgegeben, und es wird die Anzahl der Zeilen der Datei bestimmt.

Quellcode: Datei zeilenweise lesen
Verwendete Funktionen: fopen, fclose, fgets
#include <stdio.h>
#include <stdlib.h>
#define N 256 // Max. Anzahl der Bytes für eine Zeile
int main(void) {
    FILE* fp = NULL;
    fopen_s(&fp, "myfile.txt", "r");
    if (fp == NULL) {
        printf("Fehler!\n"); exit(1);
    }
    char zeile[N] = "";
    // Lese die Datei Zeile für Zeile aus
    int anzZeilen = 0;
    while (fgets(zeile, N, fp) != NULL){       
        zeile[strlen(zeile) - 1] = '\0';       
        printf("%d: %-30s ", anzZeilen, zeile); 
        printf("%d Zeichen\n", strlen(zeile));
        anzZeilen++;
    }
    printf("\nDer Text hat %d Zeilen.\n", anzZeilen);
    fclose(fp);
}
Erläuterung: Datei zeilenweise lesen
Test-Datei: myfile.txt
Die Datei myfile.txt enthält folgenden Text:
Hallo
zusammen!
Dies ist die dritte Zeile.
Bei Ausführung des Programms wird in der Konsole folgende Ausgabe angezeigt.
C Datei zeilenweise auslesen
Implementierung
Zeile 5 - 9: Die Text-Datei wird zunächst im Lesemodus geöffnet.
Zeile 10: Die String-Variable zeile wird deklariert, sie wird die einzelnen Zeilen des Textes enthalten. Die maximale Länge einer Zeile beträgt 256 Zeichen / Bytes.
Zeile 11-18: Lese die Textdatei Zeile für Zeile aus. Der Funktionsaufruf fgets(zeile, N, fp) liest max. N Zeichen in das Array zeile. Falls fgets den NULL-Zeiger zurückgibt, bedeutet dies, dass das Ende der Datei erreicht wurde oder ein Fehler passiert ist und die Schleife wird abgebrochen.

3 Strukturierte Daten lesen / schreiben

Als strukturierte oder formatierte Daten bezeichnet man tabellenartig aufgebaute Datensätze, wie sie in Excel oder Datenbanken gespeichert werden. Jede Zeile hat dieselbe Anzahl an Spalten, alle Werte einer Spalte haben denselben Datentyp. Für den Datenaustausch strukturierter Daten zwischen verschiedenen Anwendungen gibt es unterschiedliche Dateiformate: CSV, XML, JSON. Ein in der Industrie häufig verwendetes Dateiformat für den Datenaustausch zwischen Programmen und Datenbanken sind CSV-Dateien (Comma Separated Values). Das Dateiformat CSV beschreibt den Aufbau einer Textdatei, mit der strukturierte Daten gespeichert werden können. CSV-Dateien haben die Endung *.csv und werden von Excel automatisch als Tabellen angezeigt. Als Trennzeichen wird meist das Semikolon verwendet.

In C werden strukturierte Daten wie CSV-Dateien mittels fprintf und fscanf geschrieben bzw. gelesen.

Beispiel: CSV-Datei einlesen

In diesem Beispiel wird eine CSV-Datei messwerte.csv mit drei Spalten formatiert eingelesen. Jede Spalte wird in ein eigenes Array gespeichert, das dem Datentyp der Spalte entspricht.

Quellcode: Datei formatiert lesen
Verwendete Funktionen: fopen, fclose, fgets, fseek, fscanf, feop
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define M 100 // Max. Anzahl Zeilen
#define N 256 // Max. Anzahl der Bytes für eine Zeile
int main(void) {
  double sp1[M], sp2[M]; char sp3[M]; char zeile[N];
  char* filename = "messwerte.csv";FILE* fp = NULL;
  fopen_s(&fp, filename, "r");
  if (fp == NULL) {
    printf("Fehler!\n"); exit(1);
  }
  int anzZeilen = 0;
  // (1) Bestimme Anzahl der Zeilen in der Datei
  while (fgets(zeile, N, fp))
		anzZeilen++;
  printf("Die Datei hat %d Zeilen\n", anzZeilen);
  // (2) Setze den Datei-Zeiger wieder auf Dateianfang
  int result = fseek(fp, 0L, SEEK_SET);
  int zIdx = 0; // Zeilen-Zähler
  // (3) Lese die Zeilen aus
  while (!feof(fp)) {
    fscanf_s(fp, "%lf;%lf;", &sp1[zIdx], &sp2[zIdx]);
    fscanf_s(fp, "%[^\n]", sp3, 20);
    printf("Zeile %d: %.2lf %.2lf %s\n",
      zIdx + 1, sp1[zIdx], sp2[zIdx], sp3);
    zIdx++;
  }
}
Erläuterung: Datei formatiert lesen
Test-Datei messwerte.csv
Die CSV-Datei messwerte.csv enthält Messwert-Daten, die in drei Spalten aufgebaut sind: Die erste Spalte stellt eine Feuchtigkeitsmessung dar, die zweite Spalte eine Temperaturmessung, die dritte Spalte eine Bewertung: Luftfeuchtigkeit 80% und Temperatur 23 Grad wird als "feucht" bewertet.
42;21;perfekt
45;19;perfekt
69;23;zu feucht
80;21;zu feucht
21;13;zu trocken
29;14;zu trocken
Konsolenausgabe bei Ausführung des Programms
C Datei formatiert lesen
Implementierung
Zeile 9 - 12: Die Text-Datei wird zunächst im Lesemodus geöffnet.
Zeile 13 - 17: Bestimme die Anzahl der Zeilen, die die Datei enthält.
Zeile 18 - 19: Setze mittels fseek-Funktion den Datei-Zeiger wieder auf Dateianfang.
Zeile 20 - 28: Lese die einzelnen Zeilen formatiert mittels fscanf aus.


Beim Einlesen formatierter Daten mit Hilfe der Funktionen fscanf bzw. fscanf_s sind einige Vorgaben zu beachten, das es ansonsten zu Fehlern kommt.
  Passenden Datenspeicher bereitstellen
Die Arrays, die die Spalten aufnehmen sollen, müssen in Datentyp und Speichergröße mit den Spalten den Datei abgestimmt werden. Falls die Datei n Spalten hat, und es nicht zu viele sind, kann man für jede Spalte ein Array deklarieren. Falls die Spalten alle denselben Datentyp haben, kann auch ein einziges 2D-Array als Datenspeicher verwendet werden. Bei größeren Dateien bietet es sich an, dynamische Speicherallokation zu verwenden. Dafür bestimmt man zunächst die Anzahl der Zeilen der Datei, und allokiert danach Speicher mit malloc oder calloc.
 Formatzeichen mit Formatierung der Datei abstimmen
Die Angabe der formatierenden Zeichenkette muss genau zu der Formatierung der CSV-Datei passen, Falls jede Zeile der Datei das Format 10.2;10;Text\n aufweist, ist die korrekte formatierende Zeichenkette für das Auslesen der Zahlen-Spalten"%lf;%lf;". Die letzte Spalte, die nur Zeichen enthält, wird mit fscanf_s(fp, "%[^\n]", sp3, 20); ausgelesen: "%[^\n]" bedeutet, lese alle Zeichen bis zum Zeilenumbruch, die 20 bedeutet, dass maximal 20 Zeichen gelesen werden.

Tools, Quellen und weiterführende Links