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.7. Zeichenweise Lesen und Schreiben - putc/fputc und getc/fgetc            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Die Funktionen getc() und fgetc() sind das dateiorientierte Gegenstück zu getchar(). Sie werden verwendet, um einzelne Zeichen aus einem Stream zu lesen, der zuvor mit fopen() geöffnet wurde. Der Unterschied zwischen getc() und fgetc() liegt darin, dass fgetc() als eine Funktion implementiert ist und getc() ein Makro sein darf. Hier die Syntax dazu:

#include <stdio.h>

int getc(FILE *datei);
int fgetc(FILE *datei);

Folgende beiden Schreibweisen sind dabei identisch:

/* liest ein Zeichen aus der Standardeingabe */
getchar();
/* liest ebenfalls ein Zeichen aus der Standardeingabe */
fgetc(stdin);

Dazu folgt ein Listing, welches eine Datei zum Lesen öffnet und anschließend den Inhalt der Datei Zeichen für Zeichen auf dem Bildschirm ausgibt. Eingelesen wird so lange, bis das Zeichen für Dateiende oder Fehler erreicht wird (EOF).

#include <stdio.h>

int main()
{
   int c;
   FILE *datei;

   datei=fopen("test.txt","r");
   if(datei != NULL)
      {
         while((c=fgetc(datei))!= EOF)
            putchar(c);
      }
   else
      printf("Konnte Datei nicht finden bzw. öffnen!\n");
 return 0;
}

Bei diesem Programm wird zuerst versucht, eine Textdatei im Lesemodus zu öffnen. Falls dies gelungen ist, wird der Text zeichenweise ausgelesen mit

while((c=fgetc(datei))!= EOF)

und mit putchar() zeichenweise auf dem Bildschirm ausgegeben, bis ein Fehler oder EOF auftritt. Das Programm soll ein wenig erweitert werden:

#include <stdio.h>

void read_char(FILE *stream)
{
   int c;
   while((c=fgetc(stream))!=EOF)
      putchar(c);
}

int main(int argc, char *argv[])
{
   FILE *datei;
   char filename[255];

   /* Falls die Datei zum Öffnen nicht
      als Argument übergeben wurde ... */
   if(argc<2)
      {
         printf("Welche Datei wollen sie öffnen : ");
         scanf("%s",filename);
         datei = fopen(filename ,"r");
         if(datei != NULL)
            read_char(datei);
         else
            printf("Fehler beim Öffnen von %s\n",filename);
       }
   else
      {
         datei=fopen(argv[1],"r");
         if(datei != NULL)
            read_char(datei);
         else
            printf("Konnte %s nicht öffnen!\n",argv[1]);
      }
   return 0;
}

In diesem Beispiel kann die Datei, die es zu öffnen gilt, über die Kommandozeile eingegeben werden oder erst nach dem Start des Programms. Es empfiehlt sich, diese Schreibweise allgemein für Konsolenprogramme zu verwenden. Damit ist zumindest sichergestellt, dass auch Anwender, die mit dem Programm nicht vertraut sind, es bedienen können. Als Nächstes das Gegenstück der Funktionen getc() und fgetc(). Für die beiden Funktionen putc() und fputc() gilt hinsichtlich ihres Unterschieds dasselbe wie bei getc() und fgetc(). fputc() ist somit als Funktion implementiert, und putc() darf ein Makro sein. Mit putc()/fputc() kann zeichenweise in einen Stream geschrieben werden. Die Syntax dieser Funktionen lautet:

#include <stdio.h>

int putc(int quellen, FILE *ziel);
int fputc(int quellen, FILE *ziel) ;

Damit wird das Zeichen quelle in den Stream ziel geschrieben. Der Rückgabewert ist das Zeichen in quelle oder bei Fehler bzw. Dateiende EOF.

Dazu ein Listing, mit dem Sie eine Datei zeichenweise kopieren können:

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

int main()
{
   FILE *quelle, *ziel;
   int c;
   char name_q[255], name_z[255];

   printf("Name Quelldatei : ");
   scanf("%s",name_q);
   quelle=fopen(name_q,"rb");
   if(quelle==NULL)
      {
         printf("Konnte %s nicht finden bzw. öffnen!\n",name_q);
         exit(0);
      }
   else
      {
         printf("Name Zieldatei : ");
         scanf("%s",name_z);
         ziel=fopen(name_z,"w+b");
         if(ziel==NULL)
            {
               printf("Konnte Zieldatei nicht erzeugen!\n");
               exit(0);
            }
         else
            {
              /*Wir kopieren zeichenweise von quelle nach ziel */
               while((c=getc(quelle)) != EOF)
                  putc(c,ziel);
            }
      }
   return 0;
}

In diesem Beispiel werden zwei Streams verwendet - einer, mit dem die Datei geöffnet wird, um daraus zu lesen, und ein zweiter, mit dem in eine weitere geöffnete Datei geschrieben wird:

FILE *quelle, *ziel;

Passend werden diese Streams quelle und ziel benannt. Zuerst wird eine Datei zum Lesen im "rb"-Modus geöffnet. Anschließend erfolgt eine Abfrage, wie die Zieldatei heißen soll. Falls die Zieldatei nicht existiert, wird diese erzeugt. Andernfalls wird diese Datei einfach überschrieben, da der Modus "w+" verwendet wurde. Hier wird außerdem der binäre Modus eingesetzt, da der Inhalt in diesem Fall beim Kopieren nicht von Interesse ist. Unter UNIX/Linux hat das b für den Binärmodus keine Bedeutung und wird somit ignoriert:

ziel=fopen(name_z,"w+b");

Anschließend wird überprüft, ob die Datei zum Schreiben im Binärmodus geöffnet werden konnte. Danach kann zeichenweise von der Quelldatei gelesen und in die Zieldatei geschrieben werden:

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

Wenn alles problemlos verlaufen ist, wurde eine exakte Kopie der Quelldatei erstellt mit dem Namen, der als Zieldatei angegeben wurde.

Dazu ein weiteres Beispiel, wann eine zeichenweise Abarbeitung von Daten sinnvoller erscheint. Jeder, der an einer Webseite arbeitet, kennt das Problem: Viel Text muss ins HTML-Format konvertiert werden. Wird dabei einmal das Zeichen '<' vergessen, welches einen HTML-Tag eröffnet, ist manchmal der vollständige Text bis zum nächsten mit '>' schließenden Tag futsch.

Das folgende Programm soll alle Sonderzeichen ins HTML-Format konvertieren. Folgende Regeln gelten:

Ersetze das Zeichen ä durch die Zeichenfolge &auml;
Ersetze das Zeichen Ä durch die Zeichenfolge &Auml;
Ersetze das Zeichen ö durch die Zeichenfolge &ouml;
Ersetze das Zeichen Ö durch die Zeichenfolge &Ouml;
Ersetze das Zeichen ü durch die Zeichenfolge &uuml;
Ersetze das Zeichen Ü durch die Zeichenfolge &Uuml;
Ersetze das Zeichen ß durch die Zeichenfolge &szlig;
Ersetze das Zeichen < durch die Zeichenfolge &lt;
Ersetze das Zeichen > durch die Zeichenfolge &gt;
Ersetze das Zeichen & durch die Zeichenfolge &amp;
Ersetze das Zeichen " durch die Zeichenfolge &quot;

Hier der Quellcode dazu:

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

/* nchars = Anzahl der Zeichen  */
/* tag    = Sonderzeichen in HTML  */
/* ziel   = Datei, in die geschrieben wird */

void sonderzeichen(int nchars, char *tag, FILE *ziel)
{
   int i;
   char zeichen;
   for(i=0; i < nchars; i++)
      {
         zeichen=tag[i];
         putc(zeichen, ziel);
      }
}

int main(int argc, char **argv)
{
   FILE *q, *z;
   int zeichen;
   if(argc < 3)
      {
         printf("Benutzung : %s quelle ziel\n",*argv);
         exit(0);
      }

   q=fopen(argv[1], "r");
   z=fopen(argv[2], "w");
   if(q == NULL || z == NULL)
      {
         printf("Fehler bei fopen()...");
         exit(0);
      }

   while((zeichen=getc(q)) != EOF)
      {
         if(zeichen=='<')
            sonderzeichen(4,"&lt;", z);
         else if(zeichen=='>')
            sonderzeichen(4,"&gt;", z);
         else if(zeichen=='\"')
            sonderzeichen(6,"&quot;",z);
         else if(zeichen=='&')
            sonderzeichen(5,"&amp;",z);
         else if(zeichen=='ä')
            sonderzeichen(6 ,"&auml;",z);
         else if(zeichen=='Ä')
            sonderzeichen(6 ,"&Auml;",z);
         else if(zeichen=='ö')
            sonderzeichen(6 ,"&ouml;",z);
         else if(zeichen=='Ö')
            sonderzeichen(6 ,"&Ouml;",z);
         else if(zeichen=='ü')
            sonderzeichen(6 ,"&uuml;",z);
         else if(zeichen=='Ü')
            sonderzeichen(6 ,"&Uuml;",z);
         else if(zeichen=='ß')
            sonderzeichen(6 ,"&szlig;",z);
         else
            putc(zeichen, z);
      }
   return 0;
}

Schon wurde mit ein paar Zeilen Code ein Text2Html-Konverter in einer Light-Version geschrieben.

19.8. Datei (Stream) schließen - fclose            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Die Funktion fclose() schließt eine Datei (Stream), die zuvor mit fopen() geöffnet wurde. Was soll das bringen? Wenn sich ein Programm beendet, schließen sich automatisch alle noch offenen Streams. Es gibt zwei gute Gründe, dies dennoch selbst zu tun:

Die Syntax von fclose():

#include <stdio.h>

int fclose(FILE *f);

Hiermit wird der Stream geschlossen, und f vom Typ FILE ist wieder freigegeben für weitere Verwendungen. Hierzu ein kleines Listing:

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

int main()
{
   FILE *quell,*ziel;
   char quellname[20],zielname[20];
   int c, wahl;

   printf("Bitte geben sie den Namen der Zieldatei an : ");
   scanf("%19s",zielname);
   if((ziel=fopen(zielname,"a+"))==NULL)
      {
         printf("Konnte \"%s\" nicht erstellen bzw finden!\n");
         printf("...oder Sie haben unzureichende Rechte \n");
         exit(0);
      }

   do {
        printf("Welche Datei wollen sie in "
               "die Zieldatei schreiben,\n");
        printf("bzw. anhängen (name.xxx) : ");
        scanf("%19s",quellname);
        quell= fopen(quellname,"r");
        if(NULL == quell)
           printf("Konnte %s nicht öffnen!\n",quellname);
        else
           {
              while((c=getc(quell)) != EOF)
                 putc(c,ziel);
              fclose(quell);
           }
        printf("Weitere Datei an %s anhängen (1=ja/2=nein): ",
                                                    zielname);
        scanf("%1d",&wahl);
      }while(wahl == 1);
   return 0;
}

Zuerst wird eine Datei geöffnet oder - falls noch nicht vorhanden - neu erstellt. Diese Datei wird im Modus "a+" geöffnet, womit die schreibenden Daten immer an das Ende der Datei angehängt werden. Anschließend werden in der do-while-Schleife Dateien zum Lesen mit dem Modus "r" geöffnet, um den Inhalt immer an das Ende der Zieldatei zu hängen. Danach wird diese Datei wieder geschlossen:

fclose(quell);

Damit ist der Zeiger quell wieder frei zum Öffnen einer anderen Datei. Jetzt kann erneut eine Datei geöffnet werden, um diese wieder ans Ende der Zieldatei zu hängen. Der Modus "a+" eignet sich prima zum Erstellen einer Logdatei. Oder etwa zum Kontrollieren, was die Mitarbeiter so alles mit und an dem PC treiben. Das Programm soll wieder ein wenig flexibler gemacht werden. Wird z.B. Folgendes eingegeben

programm alles.txt name1.txt name2.txt home/C1/adressen.txt 

werden die Dateien mit den Namen "name1.txt", "name2.txt" und die Datei im Verzeichnis "/home/C1/adressen.txt" in einer neu erstellten Datei, hier "alles.txt", angehängt. Genauer:

<Programmname><Ziel><Quelle1><Quelle2><Quelle_n>…

Die Verwendung von zwei Streams (FILE *quelle,*ziel) in Verbindung mit der Funktion fclose() wird im folgenden Beispiel dargestellt.

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

int main(int argc,int *argv[])
{
   FILE *quell,*ziel;
   char c,i;

   if(argc<3)
      {
         printf("Fehler!!!!\n");
         printf("Mindestens 3 Argumente angeben :\n");
         printf("<Programmname><Ziel><Quelle1>"
                "(<Quelle2><Quelle_n>) \n");
          exit(0);
      }
   ziel=fopen((char *)argv[1],"a+");
   if(ziel == NULL)
      {
         printf("Konnte \"%s\" nicht erstellen bzw. nicht"
                " finden!\n" ,argv[1]);
         exit(0);
      }

   for(i=2;i<argc;i++)
      {
         if(argv[i] != NULL)
            {
               quell=fopen((char *)argv[i],"r");
               if(NULL == quell)
                  printf("Konnte %s nicht öffnen\n",argv[i]);
               else
                  {
                     while((c=getc(quell)) != EOF)
                        putc(c,ziel);
                     fclose(quell);
                  }
            }
      }
   fclose(ziel);
   return 0;
}

19.9. Formatiertes Einlesen/Ausgeben von Streams mit fprintf und fscanf            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

fprintf() und fscanf() sind die dateiorientierten Gegenstücke zu den Funktionen printf() und scanf(). Hierzu die Syntax:

#include <stdio.h>

int fprintf(FILE *f, const char *format, ...);
int fscanf(FILE *f, const char *format, ...);

Natürlich würde mit dem Folgenden

fscanf(stdin,"%d",&x);
fprintf(stdout,"Hallo Welt\n");

dasselbe erreicht wie mit

scanf("%d",&x);
printf("Hallo Welt\n");

Das sind beide Schreibweisen, Lesen bzw. Schreiben, formatiert auf die Streams stdin bzw. stdout. fprintf() wird gern benutzt, um durch den Stream stderr eine Meldung auf die Standardfehlerausgabe (Bildschirm) ungepuffert auszugeben.

Ein klassischer Fall von fprintf() und fscanf() ist das formatierte Einlesen einer CSV-Datei, die Sie mit Tabellenkalkulationsprogrammen erzeugen und ansehen können. Das Thema wurde in Kapitel 14, Arrays, bereits einmal erwähnt.

Folgende CSV-Log-Datei soll protokolliert werden. Darin steht, wer, wann und wie lange am System eingeloggt war:

20:23,12.11.2001,20:50,12.11.2001,Pinguin
12:13,13.11.2001,15:29,13.11.2001,root
16:33,13.11.2001,20:23,13.11.2001,Mr.X
23:11,13.11.2001,01:12,14.11.2001,root
10:22,14.11.2001,12:14,14.11.2001,Spock
16:33,14.11.2001,20:21,14.11.2001,Scotty

Die Kommas stellen dabei Trennzeichen dar. Folgende Variablen werden benötigt:

Uhrzeit eingeloggt, Datum, Uhrzeit ausgeloggt, Datum, User 

Der Name dieser Datei sei "log.csv". Um diese Datei auszulesen, sollen die beiden Funktionen fprintf() und fscanf() eingesetzt werden. Hier der Quellcode zum Einlesen und Ausgeben der Log-Datei:

#include <stdio.h>

int main(int argc, char **argv)
{
   FILE *CSV;
   int login_hour, login_min;
   int date_day, date_mon, date_year;
   char name[40];
   int logout_hour, logout_min;
   int date_dayx, date_monx, date_yearx;

   if(argc < 2)
      {
         fprintf(stderr, "Verwendung : %s datei.csv\n",*argv);
         exit(0);
      }
   CSV = fopen(argv[1], "r");
   if(NULL == CSV)
      {
         printf("Fehler beim Öffnen");
         exit(0);
      }

 /*Nun lesen Sie formatiert von der Datei ein ...*/
   while((fscanf(CSV,"%d:%d,%d.%d.%d,%d:%d,%d.%d.%d,%s\n",
           &login_hour,&login_min,&date_day,&date_mon,&date_year,
           &logout_hour,&logout_min,&date_dayx,&date_monx,
           &date_yearx,name)) != EOF )
       fprintf(stdout,"User:%s\nLogin um:%d:%d Uhr am %d.%d.%d\n"
                      "Logout um : %d:%d Uhr am %d.%d.%d\n\n",
           name,login_hour,login_min,date_day,date_mon,date_year,
           logout_hour,logout_min,date_dayx,date_monx,
           date_yearx);
   return 0;
}

Meistens lässt sich das Einlesen solcher Dateien allerdings nicht so leicht realisieren wie hier dargestellt. Folgendes Beispiel ist da schon wesentlich komplexer:

20:23,12.11.2001,"pinguin",20:50,12.11.2001

Versuchen Sie es zunächst ruhig selbst, diese Zeile mit fscanf() einzulesen. Das Problem liegt in diesem Beispiel beim String pinguin und den doppelten Hochkommata. Hier die Möglichkeit, wie der User pinguin ohne Gänsefüßchen ausgelesen wird:

char begrenzer;
…
while((fscanf(CSV,"%d:%d,%d.%d.%d,\"%[^'\"]%c,%d:%d,%d.%d.%d\n",
           &login_hour,&login_min,&date_day,&date_mon,&date_year,
           name,&begrenzer,&logout_hour,&logout_min,
           &date_dayx,&date_monx,&date_yearx)) != EOF )

Auf den ersten Blick mag dies zwar logisch sein, aber darauf muss man erst einmal kommen. Zwar ist dieser Ansatz, CSV-Dateien einzulesen, schon recht praktisch, doch vollkommen inflexibel. Das Programm ließt nur CSV-Dateien aus, deren Anordnung Sie kennen. Das würde heißen, für jede CSV-Datei müssten Sie den Code ändern, damit dieser wie eine Schablone in seine Form passt.

Gut eignet sich auch fprintf() zum Erstellen von dynamischen Textdateien. Hierzu ein Beispiel, wie Sie dynamisch Webseiten erstellen können. Dafür wird zuvor einfach der Text2HTML-Konverter aus dem vorigen Abschnitt erweitert:

#include <stdio.h>

void html_head(FILE *ziel)
{
   fprintf(ziel, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD"
                 " HTML 4.0 Transitional//EN\">\n");
   fprintf(ziel,"<html><head><title>Test-Webseite"
                "</title></head><body>\n");
   fprintf(ziel,"<pre>\n");
   fprintf(ziel,"<p style=\"margin-right:0.8cm; "
                " margin-left:0.5cm\" align=\"justify\">\n");
}

void html_end(FILE *ziel)
{
   fprintf(ziel,"</pre></p></body></html>\n");
}

void sonderzeichen(int nchars, char *tag, FILE *ziel)
{
   int i;
   char zeichen;
   for(i=0; i<nchars; i++)
      {
         zeichen=tag[i];
         putc(zeichen, ziel);
      }
}

int main(int argc, char **argv)
{
   FILE *q, *z;
   int zeichen;
   int i=0;

   if(argc < 3)
      {
         printf("Benutzung : %s quelle ziel\n",*argv);
         exit(0);
      }

   q=fopen(argv[1], "r");
   z=fopen(argv[2], "w");

   if(q == NULL || z == NULL)
      {
         printf("Fehler bei fopen()…");
         exit(0);
      }
   /*Kopfzeile für HTML-Dokument*/
   html_head(z);

   while((zeichen=getc(q)) != EOF)
      {
         if(zeichen=='<')
            sonderzeichen(4,"&lt;", z);
         else if(zeichen=='>')
            sonderzeichen(4,"&gt;", z);
         else if(zeichen=='\"')
            sonderzeichen(6,"&quot;",z);
         else if(zeichen=='&')
            sonderzeichen(5,"&amp;",z);
         else if(zeichen=='ä')
            sonderzeichen(6 ,"&auml;",z);
         else if(zeichen=='Ä')
            sonderzeichen(6 ,"&Auml;",z);
         else if(zeichen=='ö')
            sonderzeichen(6 ,"&ouml;",z);
         else if(zeichen=='Ö')
            sonderzeichen(6 ,"&Ouml;",z);
         else if(zeichen=='ü')
            sonderzeichen(6 ,"&uuml;",z);
         else if(zeichen=='Ü')
            sonderzeichen(6 ,"&Uuml;",z);
         else if(zeichen=='ß')
            sonderzeichen(6 ,"&szlig;",z);
         else if(zeichen=='\n') /* Zeilenumbruch */
            sonderzeichen(4, "<br>", z);
         else if(zeichen==' ')  /* Leerzeichen */
            sonderzeichen(6, "&nbsp;", z);
         else
            putc(zeichen, z);
      }
   /*Ende von HTML-Datei*/
   html_end(z);
   return 0;
}

Geben Sie nun in der Kommandozeile Folgendes ein (der Programmname sei t2html):

t2html myfile.txt myfile.hmtl 

Jetzt finden Sie im Verzeichnis eine HTML-Datei namens myfile.html, die aus der Datei myfile.txt mit dem Programm t2html erzeugt wurde. Diese HTML-Datei können Sie nun mit Ihrem Lieblingsbrowser öffnen und ansehen.

Weiter mit 19.10. Standardstreams in C            zum Inhaltsverzeichnis