Was währe eine Programmiersprache, ohne das Daten auf einen Datenträger gespeichert werden können. In diesem sehr Umfangreichen Kapitel geht es Ausschließlich um den Fluss der Daten. Damit ist die Eingabe und Ausgabe von Daten gemeint. C C++ C/C++ Datei Stream Eingabe Ausgabe fopen fgets fputs fread fwrite fgetc fputc gets puts sscanf sspintf ... Datei Stream Eingabe Ausgabe fopen fgets fputs fread fwrite fgetc fputc gets puts sscanf sspintf ... Kapitel 19: Datei Ein- Ausgabe

19.10. Standardstreams in C            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Für jedes ausführbare Programm werden die folgenden Standard-Streams bereitgestellt:

Abbildung 19.1: Standard-Streams in C
Abbildung 19.1: Standard-Streams in C

In der Regel ist der Stream stdin für die Eingabe über die Tastatur eingestellt. Für die Ausgabe auf dem Bildschirm sind die Streams stdout und stderr eingerichtet. Der Unterschied zwischen stdout und stderr liegt darin, dass stderr nicht gepuffert wird - im Gegensatz zu stdout. Daher liest jedes scanf() und fscanf()

scanf("%s",string);
fscanf(stdin,"%s",string);

von der Standardeingabe (Tastatur) und jedes printf() und fprintf()

printf("Hallo Welt\n");
fprintf(stdout,"Hallo Welt\n");
fprintf(stderr,"Fehler im Programm\n");

wird auf dem Bildschirm ausgegeben.

19.10.1 Standard-Streams umleiten
Mit dem folgenden Listing will ich Ihnen zeigen, wie Sie diese Streams umleiten können:

#include <stdio.h>

int main()
{
   int c;
   while((c=getc(stdin)) != EOF)
      putc(c,stdout);
   return 0;
}

Es wird so lange von stdin (Tastatur) eingelesen, bis EOF (STRG + Z bei Windows/MS-DOS und STRG + D bei UNIX) erzeugt wird. Bei Betätigung von ENTER wird die Eingabe auf stdout (Bildschirm) ausgegeben. Diese Standard-Streams lassen sich aber auch umleiten, etwa mit folgender Eingabe in der Kommandozeile:

programmname < test.txt 

Abbildung 19.2: Standardeingabe umleiten (stdin)
Abbildung 19.2: Standardeingabe umleiten (stdin)

Mit dieser Umleitung wurde die Datei "test.txt" über die Standardeingabe (stdin) zum Programm umgeleitet. Damit lässt sich allerdings nun keine Eingabe mehr über die Tastatur vornehmen. Natürlich kann die Standardausgabe ebenso umgeleitet werden:

programmname < test.txt > kopietext.txt 

Abbildung 19.3: Standardeingabe und Standardausgabe umgeleitet
Abbildung 19.3: Standardeingabe und Standardausgabe umgeleitet

Die Datei "test.txt" wird damit auf die Standardeingabe (stdin) und die Datei "kopiertext.txt" auf die Standardausgabe (stdout) umgeleitet. In diesem Fall wird gar nichts mehr auf dem Bildschirm ausgegeben, da die Standardausgabe (stdout) umgeleitet wurde zu "kopiertext.txt". Natürlich wäre auch eine solche Umleitung möglich:

programmname > text.txt  

Hier wurde nur die Standardausgabe umgeleitet. Damit würde alles, was mit der Tastatur eingegeben wurde, in die Datei "text.txt" anstatt auf den Bildschirm geschrieben.

Abbildung 19.4: Standardausgabe umgeleitet
Abbildung 19.4: Standardausgabe umgeleitet

Auf Systemen, die den POSIX-Standard erfüllen (UNIX, Linux, FreeBSD ...), aber mittlerweile auch auf vielen anderen Compilern, sollten Sie statt der Standard-Streams stdin, stdout und stderr folgende Konstanten benutzen:

Diese Konstanten sind in der Headerdatei <unistd.h> deklariert, die daher im Programm mit eingebunden werden muss.

19.11. Fehlerbehandlung von Streams - feof, ferror, clearerr            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

In diesem Abschnitt folgen einige Funktionen, die nicht so häufig verwendet werden, aber durchaus von Bedeutung sind. Denn gerade die Fehlerbehandlung wird bei Programmen oft vernachlässigt. Zuerst die Syntax zu feof():

#include <stdio.h>

int feof(FILE *datei);

Mit dieser Funktion können Sie einen Stream auf das EOF-Flag testen, ob es gesetzt ist oder nicht. Hierzu ein Beispiel:

#include <stdio.h>

int main()
{
   int c;
   while(c=getc(stdin))
      if(feof(stdin)!=0)
         break;
      else
         putc(c,stdout);
   return 0;
}

Das Programm dürfte Ihnen noch vom Abschnitt zuvor bekannt sein. Anstatt

while((c=getc(stdin)) != EOF)  

wurde Folgendes geschrieben

while(c=getc(stdin))
   if(feof(stdin)!=0)

Statt zu testen, ob der Wert der Variablen c demjenigen von EOF entspricht, wurde mit feof() geprüft, ob das EOF-Flag gesetzt ist. Falls nicht, gibt die Funktion 0 zurück, falls aber EOF gesetzt ist, gibt die Funktion ungleich 0 zurück. Wird die Funktion feof() bei längeren Programmen nochmals benötigt, muss vorher mit

clearerr(stdin);

das EOF-Flag des entsprechenden Streams wieder auf 0 gesetzt werden. Natürlich können Sie anstatt des Streams stdin auch jeden anderen offenen Stream verwenden.

Die Syntax von ferror() lautet:

#include <stdio.h>

int ferror(FILE *datei);

Die Funktion ferror() ist ähnlich wie feof(), nur dass die Datei auf das Fehler-Flag überprüft wird. Auch hier ist im Prinzip der Aufruf

if((quelle=fopen(argv[1],"r")) == NULL) 

gleichwertig mit

quelle=fopen(argv[1],"r");
   if(ferror(quelle))

Auch bei ferror() muss bei nochmaliger Verwendung die Funktion clearerr() verwendet werden, um den Fehler-Flag zurückzusetzen. Die Syntax von clearerr() lautet:

#include <stdio.h>

void clearerr(FILE *datei);

clearerr() dient zum Zurücksetzen des EOF- und des Fehler-Flags bei Streams.

19.12. Gelesenes Zeichen in die Eingabe zurückschieben - ungetc            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Mit der Funktion ungetc() können Sie das zuletzt gelesene Zeichen wieder zurück in den Stream schieben. Die Syntax:

#include <stdio.h>

int ungetc(int ch, FILE *datei);

ungetc() schiebt das zuletzt mit der Funktion fgetc() oder fread() gelesene Zeichen ch in den Stream datei zurück. Im Fall eines Fehlers gibt diese Funktion EOF zurück. Damit ist das Zeichen ch das erste, das beim nächsten Lesen aus dem Stream datei wieder gelesen wird.

Dies gilt allerdings nicht mehr, wenn vor dem nächsten Lesevorgang eine der Funktionen fflush(), rewind(), fseek() oder fsetpos() aufgerufen wurde.

Ein Beispiel zu ungetc(). Das Auslesen einer ständig wachsenden Textdatei. Das Programm liest zeichenweise aus einem Stream und gibt diese auch zeichenweise wieder auf die Standardausgabe aus, bis EOF erreicht wird. Anschließend wird das zuletzt gelesene Zeichen (nicht EOF) wieder zurück in den Stream geschoben. Das Ganze wird in einer Endlosschleife ausgeführt. Hier der Code dazu:

#include <stdio.h>
#include <stdlib.h>
/*Bitte anpassen*/
#define DATEI "datei.txt"

int main()
{
   FILE *fp;
   int c;
   fp = fopen(DATEI,"r");
   if(fp == NULL)
      {
         fprintf(stderr, "Konnte %s nicht öffnen\n",DATEI);
         exit(0);
      }

   while(1)
      {
         while(c=fgetc(fp)) /* Zeichenweise einlesen */
            if(c==EOF)      /* Ist es EOF */
               {
                  ungetc(c,fp);  /* Letztes Zeichen zurück */
               }
            else
               {
                 fputc(c, stdout); /* Ausgeben */
               }
      }
   /*Wird nie erreicht*/
   fclose(fp);
   return 0;
}

Bei diesem Listing wird davon ausgegangen, dass eine Datei mit dem Namen "datei.txt" im selben Verzeichnis wie das Listing existiert. Der Inhalt dieser Datei sei folgender:

Eine Zeile in der Textdatei
Die zweite Zeile ist diese hier

Übersetzen Sie das Listing und starten das Programm. Öffnen Sie jetzt die Textdatei "datei.txt" und fügen Sie einen weiteren Text ein. Zum Beispiel:

Eine Zeile in der Textdatei
Die zweite Zeile ist diese hier
Diese Zeile ist neu hinzugekommen

Speichern Sie diesen Text wieder und beachten Sie die weitere Ausführung des Programms. Die neu hinzugekommene Zeile wird ebenfalls ausgegeben. Theoretisch ließen sich damit in einem Netzwerk einzelne Dateien überwachen. Statt der Ausgabe auf dem Bildschirm, könnte hierfür eine Nachricht an den Administrator geschickt werden.

Vermutlich stellen Sie sich die Frage, wie es möglich ist, dass trotz eines EOF-Flags das Programm tadellos arbeitet, ohne bspw. die Funktion clearerr() aufzurufen. Das liegt daran, dass die Funktion ungetc() das EOF-Flag löscht und somit immer wieder nach dem Erreichen des Dateiendes ein Zeichen zurückschieben kann. ungetc() kann aber keine EOF-Konstante zurückschieben.

19.13. (Tastatur)Puffer leeren - fflush            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Diese Funktion dürfte Ihnen aus Kapitel 6, Formatierte Eingabe mit scanf(), bekannt vorkommen, als es darum ging, ein im Tastaturpuffer befindliches Zeichen wie '\n' (Newline-Zeichen) zu entfernen. Die Syntax von fflush():

#include <stdio.h>

int fflush(FILE *datei);

Mit fflush() werden alle Inhalte von einem noch nicht geleerten Puffer eines Streams übertragen, die dem FILE-Zeiger datei zugeordnet sind (oder auch Standard-Streams). Das heißt kurz und bündig, die dem Stream zugeordneten Puffer werden geleert. Falls die Datei zum Schreiben geöffnet ist, werden die sich noch im Puffer befindlichen Zeichen physikalisch in die Datei geschrieben. War eine Datei zum Lesen geöffnet, werden die noch nicht gelesenen Zeichen im Eingabepuffer gelöscht. fflush() gibt bei Erfolg 0 zurück, andernfalls EOF.

Es scheint aber nicht ganz klar, ob dies auch für Linux gültig ist - wie Sie dies auch auf der man-Page zu fflush() nachlesen können. Auf Linux-Systemen will fflush() partout nicht funktionieren. Nehmen Sie etwa folgendes Programm:

#include <stdio.h>

int main()
{
   char x,y;

   printf("Bitte einen Buchstaben eingeben : ");
   scanf("%c",&x);
   fflush(stdin);
   printf("Bitte noch einen Buchstaben eingeben : ");
   scanf("%c",&y);
   printf("Sie gaben ein %c und %c \n",x,y);
   return 0;
}

Sie können das Programm verbiegen und verdrehen wie Sie wollen, immer wird die zweite Ausgabe durch das sich im Puffer befindliche '\n'-Zeichen überdrückt. In Kapitel 6 finden Sie Beispiele, wie das Problem gelöst werden kann. Der ANSI C-Standard schreibt aber auch nicht vor, wie die Funktion fflush() auf den Stream stdin zu reagieren hat. Das Verhalten ist somit nicht definiert und kann funktionieren oder auch nicht.

19.13.1 Pufferung
Eine kurze Erklärung zur Pufferung: Die Standardeinstellung ist bei ANSI C Compilern die Vollpufferung. Dies ist auch sinnvoller und schneller als keine Pufferung, da wenige Lese- und Schreiboperationen etwa auf der Festplatte, der Diskette oder dem Arbeitsspeicher stattfinden. Die Puffergröße ist abhängig vom Compiler, liegt aber meistens bei 512 und 4096 Bytes. Die Größe ist in der Headerdatei <stdio.h> mit der Konstante BUFSIZ angegeben. Bei einer Pufferung, die zeichenweise eingestellt ist, würde ein Kopiervorgang zum Beispiel so ablaufen:

Lese aus Datei ein Zeichen (von z.B. Tastatur)
Schreibe in eine Datei ein Zeichen (z.B. Diskette)
Lese aus Datei ein Zeichen (von z.B. Tastatur)
Schreibe in eine Datei ein Zeichen (z.B. Diskette)
Lese aus Datei ein Zeichen (von z.B. Tastatur)
Schreibe in eine Datei ein Zeichen (z.B. Diskette)
usw. Zeichen für Zeichen

Bei einer Datei mit 100 Bytes wären das 100 Zugriffe zum Lesen von der Tastatur im Wechsel mit 100 Zugriffen zum Schreiben auf die Diskette.

Bei Vollpufferung läuft dies so: Es wird so lange gelesen, bis der Puffer voll ist (BUFSIZE), und dann wird geschrieben. Im obigen Beispiel würde bei Vollpufferung einmal gelesen und einmal geschrieben.

19.14. Stream positionieren - fseek, rewind und ftell            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Zuerst die Syntax von fseek():

#include <stdio.h>

int fseek(FILE *datei, long offset, int origin);

Mit fseek() kann der Schreib-/Lesezeiger des Streams datei verschoben werden. Die Positionierung wird mit offset und origin angegeben. origin gibt den Bezugspunkt an, von wo ab der Schreib-/Lesezeiger verschoben werden soll. offset gibt an, wie weit von diesem Bezugspunkt aus der Dateizeiger verschoben wird. Für origin sind drei symbolische Konstanten in der Headerdatei <stdio.h> deklariert:

Symbol Wert Offset-Rechnung ab
SEEK_SET 0 Anfang der Datei
SEEK_CUR 1 Aktuelle Position
SEEK_END 2 Ende der Datei

Tabelle 19.6: Bezugspunkt für die Positionierung

Ein kleines Beispiel, welches die Funktion von fseek() demonstrieren soll:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   FILE *quelle,*fehler;
   int c,ausw;
   char datei[20];
   long pos=0;
   printf("Welche Datei wollen Sie Öffnen : ");
   scanf("%s",datei);
   fflush(stdin);

   if((quelle=fopen(datei,"a+")) == NULL)
      {
         if((fehler=fopen("fehler.log","a+")) != NULL)
            {
              fprintf(fehler,"Datei %s ließ sich nicht oeffnen\n"
                                                         ,datei);
              fprintf(stderr,"Konnte %s nicht oeffnen\n",datei);
              exit(0);
            }
         fprintf(stderr,"Konnte %s nicht oeffnen\n",datei);
         exit(1);
      }

   /*Das Zeichen '*' soll das Ende unserer Eingabe markieren*/
   printf("Eingabe machen und mit '*' beenden\n");
   while((c=getc(stdin)) != '*')
      putc(c,quelle);

   /*Sie setzen den Zeiger quelle an den Anfang der Datei*/
   fseek(quelle, 0L, SEEK_SET);

   /*Sie geben die ganze Datei auf dem Bildschirm aus*/
   printf("\nAusgabe der kompletten Datei : \n");
   while((c=getc(quelle)) != EOF)
      putc(c,stdout);

   /*Zur Demonstration gehen Sie von der aktuellen Position*/
   /*10 Zeichen zurück und geben die letzten 10 Zeichen aus*/
   printf("\nDie letzten 10 Zeichen : ");
   fseek(quelle, -10L, SEEK_CUR);
   while((c=getc(quelle)) != EOF)
      putc(c,stdout);

   /*Sie legen selbst fest, wie viel Zeichen wir vom Start aus*/
   /*einrücken wollen.*/
   printf("\nAnzahl der Stellen einrücken (vom Anfang): ");
   scanf("%ld",&pos);
   fflush(stdin);

   fseek(quelle, 0L, SEEK_SET);
   fseek(quelle, pos,SEEK_CUR);

   while((c=getc(quelle)) != EOF)
      putc(c,stdout);

   return 0;
}

Abbildung 19.5: Verschieben des Schreib-/Lesezeigers mit der Funktion fseek()
Abbildung 19.5: Verschieben des Schreib-/Lesezeigers mit der Funktion fseek()

Zuerst wird eine Datei geöffnet. Falls dies nicht gelingt, wird eine Datei mit dem Namen "fehler.log" beschrieben. Anschließend wird so lange eine Eingabe gemacht, bis das Zeichen '*' eingegeben wurde. Die Eingabe wird an das Ende der Datei gehängt oder es wird, falls nicht vorhanden, eine entsprechende Datei erzeugt ("a+"-Modus). Dann wird mit

fseek(quelle, 0L, SEEK_SET); 

der Schreib-/Lesezeiger des Streams quelle an den Anfang der Datei gesetzt, da SEEK_SET als Anfang der Datei deklariert ist. Wenn stattdessen Folgendes verwendet würde

fseek(quelle, 10L, SEEK_SET); 

wäre der Schreib-/Lesezeiger vom Anfang der Datei um zehn Bytes nach vorne verschoben, also dann zehn Zeichen vom Dateianfang entfernt. Anschließend wird die vollständige Datei auf dem Bildschirm ausgegeben. Jetzt befindet sich der Schreib-/Lesezeiger am Ende der Datei. Als Nächstes wird mit

fseek(quelle, -10L, SEEK_CUR);

der Schreib-/Lesezeiger um zehn Stellen von der aktuellen Position (SEEK_CUR) zurückgeschoben. Es ist also auch möglich, negative Werte für offset anzugeben. Dabei werden die zehn letzten Zeichen auf dem Bildschirm ausgegeben. Dann erfolgt eine Abfrage, um wie viele Stellen der Schreib-/Lesezeiger des Streams quelle vom Anfang der Datei verschoben werden soll. Dies wird gleich programmtechnisch umgesetzt mit:

fseek(quelle, pos,SEEK_CUR);

Benötigen Sie die aktuelle Position des Schreib-/Lesezeigers im Stream datei, können Sie diesen mit der Funktion ftell() ermitteln. Die Syntax:

long ftell(FILE *datei);

Falls dabei ein Fehler auftritt, liefert diese Funktion einen Wert kleiner als 0 zurück. Bei Erfolg gibt sie die aktuelle Position des Schreib-/Lesezeigers in Byte zurück. Die Funktion ftell() können Sie ebenso einsetzen, um die Größe einer Datei in Byte zu ermitteln:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   FILE *quelle;
   char datei[20];

   printf("Welche Datei wollen Sie Öffnen : ");
   scanf("%s",datei);

   if((quelle=fopen(datei,"r")) == NULL)
      {
         fprintf(stderr,"Konnte %s nicht oeffnen\n",datei);
         exit(1);
      }

   /*Wir setzen den FILE-Zeiger ans Ende der Datei*/
   fseek(quelle, 0L, SEEK_END);
   printf("Die Datei ist %ld Bytes gross!!\n",ftell(quelle));
   return 0;
}

Nachdem mit fseek() der FILE-Zeiger an das Ende der Datei positioniert wurde, kann mit ftell() die Position und auch die Größe in Byte abgefragt werden. ftell() liefert als Rückgabewert den Datentyp long.

Es existiert auch eine andere Möglichkeit, den Stream wieder zurück zum Anfang der Datei zu setzen. Statt mit

fseek(quelle, 0L, SEEK_SET); 

kann dies auch mit der folgenden Funktion realisiert werden:

rewind(quelle);

Beide Funktionen erfüllen denselben Zweck. Die Syntax von rewind() lautet:

#include <stdio.h>

void rewind(FILE *datei);

19.15. Stream positionieren - fsetpos, fgetpos            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Neben den Funktionen fseek() und ftell() gibt es noch eine weitere Möglichkeit zum Positionieren eines Streams. Wobei diese beiden Funktionen häufig gar nicht mehr erwähnt werden, da sie nicht mehr bieten als fseek() und ftell(). Hier die Syntax der beiden Funktionen:

#include <stdio.h>

int fsetpos(FILE *datei, const fpos_t *pos);
int fgetpos(FILE *datei, fpos_t *pos);

Mit fsetpos() wird der Schreib-/Lesezeiger auf die Adresse von pos gesetzt. Die "Variable" fpos_t ist ein so genannter primitiver Datentyp. Die Adresse, die für pos verwendet wird, sollte mit dem Aufruf der Funktion fgetpos() ermittelt werden. Hierzu ein Beispiel:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   FILE *quelle;
   int c;
   char datei[20];
   fpos_t pos;

   printf("Welche Datei wollen Sie Öffnen : ");
   scanf("%s",datei);

   if((quelle=fopen(datei,"r")) == NULL)
      {
         fprintf(stderr,"Konnte %s nicht oeffnen!!\n",datei);
         exit (0);
      }

   /*Wir lesen die aktuelle Position unseres FILE-Zeigers*/
   fgetpos(quelle,&pos);
   printf("Der Positionszeiger zeigt auf Byte : %ld\n",pos);

   while((c=getc(quelle)) != EOF)
      putc(c,stdout);
   printf("Groesse der Datei= Byte : %ld\n",ftell(quelle));

   /*Wir setzen den FILE-Zeiger wieder zum
     Anfang der Datei*/
   fsetpos(quelle,&pos);
   printf("Wir sind wieder an Position %ld\n",pos);
   return 0;
}

Nachdem eine Datei zum Lesen geöffnet wurde, wird mit

fgetpos(quelle,&pos); 

die aktuelle Position des FILE-Zeigers quelle ermittelt. Die Position steht anschließend in dem zu Beginn des Programms festgelegten Datentyp

fpos_t pos;

Die Adresse wird mit dem Adressoperator (&) in der Funktion fgetpos() an pos übergeben. Danach wird die Datei ausgelesen mitsamt der Größe in Byte. Mit

fsetpos(quelle,&pos); 

wird der Stream-Zeiger wieder an den Dateianfang gesetzt. Dies hätten Sie auch mit folgender Funktion erreichen können:

rewind(quelle); 

Richtig eingesetzt sind diese beiden Funktionen recht nützlich, falls eine bestimmte Position in der Datei gespeichert wird, um später wieder darauf zurückzuspringen.

Weiter mit 19.16. Zeilenweise Ein-/Ausgabe            zum Inhaltsverzeichnis