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.29. Schreiben und Lesen - write und read            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Zuerst die Syntax der Funktion write ():

#include <unistd.h> /* für UNIX/LINUX */
#include <io.h>     /* für MS-DOS     */

int write(int fh, const void *puffer, size_t bytezahl);

Mit der Funktion write() wird unformatiert in die Datei mit dem File-Deskriptor fh geschrieben. Um den geeigneten File-Deskriptor zu erhalten, muss die Datei zuvor mit open() oder create() geöffnet werden. Dann schreibt write() von der Datei mit dem fh-File-Deskriptor bytezahl Bytes in die Speicheradresse von puffer. Dieser ist wieder ein typenloser void-Zeiger und kann sich somit jeden beliebigen Datentyps annehmen. Bei einem Fehler liefert diese Funktion den Wert -1 zurück, ansonsten die Anzahl der erfolgreich geschriebenen Bytes. Hierzu ein einfaches Beispiel mit write():

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#ifdef __unix__
      #include <unistd.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
      #include <io.h>
#endif

int main()
{
 int fh;
 char puffer[100];

 strcpy(puffer,"Dieser Text steht in \"test.txt\"\n");

 if((fh=open("test.txt",O_RDWR|O_CREAT|O_TRUNC)) == -1)
  {
   printf("Fehler beim Öffnen der Datei \"test.txt\"\n");
   exit (0);
  }

 if((write(fh, &puffer, sizeof(puffer))) == -1)
  {
   printf("Fehler bei write........\n");
   exit (1);
  }
 printf("Datei wurde erfolgreich in \"test.txt\" geschrieben\n");
 return 0;
}

Zuerst wird mit strcpy() ein String in das Array puffer kopiert. Danach wird mit open() die Datei "test.txt" geöffnet bzw. erzeugt. In dem von open() zurückgegebenen File-Deskriptor werden dann mit der Funktion write() von der Adresse puffer Bytes der Anzahl sizeof(puffer) geschrieben.

Die Funktion write() eignet sich genauso wie fwrite() dazu, ganze Strukturen auf einmal zu schreiben, wie das folgende Listing demonstriert:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys\stat.h>
#ifdef __unix__
      #include <unistd.h>
      #include <sys/stat.h>
      #include <sys/types.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
      #include <io.h>
#endif
#define MAXADRESSEN 10
#define MAX 30

struct kunde  {
               char name[MAX];
               char vorname[MAX];
               int kundenummer;
               char ort[MAX];
               char strasse[MAX];
               int hausnummer;
               int vorwahl;
               int telefonnr;
              };

struct kunde k[MAXADRESSEN];
static int counter=0;

void neukunde()
{
   int fh;
   if(counter==MAXADRESSEN)
      printf("Kein Speicherplatz mehr frei!!!\n");
   else
      {
         printf("Name...................: ");
         fgets(k[counter].name, MAX, stdin);
         printf("Vorname................: ");
         fgets(k[counter].vorname, MAX, stdin);
         k[counter].kundenummer=counter;
         printf("Ort....................: ");
         fgets(k[counter].ort, MAX, stdin);
         printf("Strasse................: ");
         fgets(k[counter].strasse, MAX, stdin);
         printf("Hausnummer.............: ");
         do{
             scanf("%d",&k[counter].hausnummer);
           }while(getchar() != '\n');
         printf("Vorwahl................: ");
         do{
             scanf("%d",&k[counter].vorwahl);
           }while(getchar() != '\n');
         printf("Telefonnummer..........: ");
         do{
             scanf("%d",&k[counter].telefonnr);
           }while(getchar() != '\n');
         if((fh=creat("kunden.dat",S_IREAD|S_IWRITE)) == -1)
            printf("Konnte\"kunden.dat\" nicht öffnen\n");
         else if((write(fh,&k,sizeof(k))) == -1)
            printf("Konnte nicht in \"kunden.dat\" schreiben\n");
         else
            counter++;
      }
}

int main()
{
   int wahl;

   do{
        printf("\t1: Neuen Kunden eingeben\n\n");
        /*printf("\t2: Kunden ausgeben\n\n");*/
        printf("\t3: Programmende\n\n");
        printf("\tEingabe :> ");
        do{
            scanf("%d",&wahl);
          }while(getchar() != '\n');
        switch(wahl)
           {
              case 1 : neukunde(); break;
              /*case 2 : lese(); break;*/
              case 3 : printf("bye\n"); break;
              default: printf("Falsche Eingabe!!!\n");
           }
     }while(wahl != 3);
   return 0;
}

Zuerst werden in der Funktion neukunde() die Daten an die Struktur übergeben. Anschließend wird mit

if((fh=creat("kunden.dat",S_IREAD|S_IWRITE)) == -1)

eine Datei namens "kunden.dat" zum Lesen und Schreiben erzeugt. Jetzt kann mit

else if((write(fh,&k,sizeof(k))) == -1)

in diese Datei über den File-Deskriptor fh von der Adresse struct kunde k mit Größe der Struktur (sizeof(k)) geschrieben werden. Anschließend wird counter inkrementiert. Ein wenig verwirrend können die if- else if -else-Bedingungen sein. Aber bei Korrektheit werden alle drei ausgeführt, solange keine der Bedingungen -1 zurückliefert. Jetzt befindet sich im Verzeichnis, in dem das Programm ausgeführt wird, eine Datei namens "kunden.dat". Wird diese Datei mit einem Texteditor geöffnet, könnte man meinen, das Schreiben mit write() hätte nicht geklappt. Aber wie bereits erwähnt, es wird unformatiert in eine Datei geschrieben. Und dies lässt sich nun mal nicht mit einem Texteditor lesen.

Anmerkung
 

Das Schreiben mit write() wird über einen Puffercache durchgeführt, bevor wirklich auf die Festplatte, Diskette usw. geschrieben wird. Dieses delayed write birgt bei einem Systemabsturz die Gefahr, dass im Cache befindliche Daten nicht physikalisch auf Festplatte oder Diskette geschrieben werden. In diesem Fall können Sie die Datei zum Schreiben im O_SYNC-Modus öffnen. Dieser Modus wartet bei jedem physikalischen Schreibvorgang bis dieser fertig ist und liest dann erst wieder Daten ein. Der Modus hat leider aber den Nachteil, schrecklich langsam zu sein. Für Linux gibt es hier zwei Funktionen, sync() und fsync(), die in diesem Buch allerdings nicht behandelt werden. Linux-User lesen bitte entsprechende man-Pages, falls Sie diese Funktionen benötigen.

Jetzt folgt das Gegenstück zur Funktion write(). Zuerst die Syntax:

int read(int fh, const void *puffer, site_t bytezahl); 

Mit der Funktion read() werden bytezahl Bytes aus der Datei mit dem File-Deskriptor fh gelesen. Die Daten werden in die Adresse von puffer abgelegt. Zuvor muss natürlich die Datei mit open() geöffnet werden. Auch hier liefert die Funktion bei Fehler -1, ansonsten, wenn alles richtig verlief, 0 zurück. Hierzu ein Listing, welches eine Datei kopiert:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#ifdef __unix__
      #include <unistd.h>
      #include <sys/stat.h>
      #include <sys/types.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
      #include <io.h>
      #include <sys\stat.h>
#endif

#define MAXBYTES 1024

int main(int arg, char **argv)
{
   int in,out,count;
   char buffer[MAXBYTES];

   if(argc < 3)
      {
         printf("Aufruf: programmname quelldatei zieldatei\n");
         exit(0);
      }

   if((in=open(*++argv,O_RDONLY)) == -1)
      printf("Fehler open %s\n",argv);
   if((out=open(*++argv,O_WRONLY|O_TRUNC|O_CREAT))== -1)
      printf("Fehler open %s\n",argv);
   while(count = read(in,buffer,MAXBYTES))
      write(out,buffer,count);
   close(in);
   close(out);
   return 0;
}

Damit wird die Datei, die als zweites Argument in der Kommandozeile angegeben wird, in die Datei, welche als drittes Argument angegeben wird, kopiert.

Jetzt soll das erste Programm aus dem vorigen Abschnitt zu write() mit der Funktion read() ergänzt werden:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#ifdef __unix__
      #include <unistd.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
      #include <io.h>
#endif

int main()
{
   int fh;
   char puffer[100];
   char pufferneu[100];

   strcpy(puffer,"Dieser Text steht in \"test.txt\"\n");

   if((fh=open("test.txt",O_RDWR|O_CREAT|O_TRUNC)) == -1)
      {
         printf("Fehler beim Öffnen der Datei \"test.txt\"\n");
         exit (0);
      }

   if((write(fh, &puffer, sizeof(puffer))) == -1)
      {
         printf("Fehler bei write........\n");
         exit (0);
      }
   close(fh);

   if((fh=open("test.txt",O_RDONLY)) == -1)
      {
         printf("Fehler beim 2. Oeffnen von test.txt\n");
         exit (0);
      }

   if((read(fh, &pufferneu, sizeof(pufferneu))) == -1)
      {
         printf("Fehler bei read.........\n");
         exit (3);
      }
   printf("%s",pufferneu);
   close(fh);
   return 0;
}

Bis zur Funktion write() soweit für Sie nichts Neues. Mit read() wird hier die Größe von sizeof(pufferneu) Bytes mit dem File-Deskriptor fh in die Adresse von pufferneu gelegt. Das Programm dürfte keinem mehr Kopfzerbrechen bereiten.

Daher soll auch das zweite obige Listing mit der Funktion read() bestückt werden. Schließlich wollen Sie die Daten, die geschrieben wurden, auch wieder lesen können:

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

#ifdef __unix__
      #include <unistd.h>
      #include <sys/stat.h>
      #include <sys/types.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
      #include <io.h>
      #include <sys\stat.h>
#endif
#define MAXADRESSEN 10
#define MAX 30

struct kunde  {
               char name[MAX];
               char vorname[MAX];
               int kundenummer;
               char ort[MAX];
               char strasse[MAX];
               int hausnummer;
               int vorwahl;
               int telefonnr;
              };

struct kunde k[MAXADRESSEN];
static int counter=0;

void neukunde()
{
   int fh;
   if(counter==MAXADRESSEN)
      printf("Kein Speicherplatz mehr frei!!!\n");
   else
      {
          printf("Name...................: ");
         fgets(k[counter].name, MAX, stdin);
         printf("Vorname................: ");
         fgets(k[counter].vorname, MAX, stdin);
         k[counter].kundenummer=counter;
         printf("Ort....................: ");
         fgets(k[counter].ort, MAX, stdin);
         printf("Strasse................: ");
         fgets(k[counter].strasse, MAX, stdin);
         printf("Hausnummer.............: ");
         do{
            scanf("%d",&k[counter].hausnummer);
           }while(getchar() != '\n');
         printf("Vorwahl................: ");
         do{
            scanf("%d",&k[counter].vorwahl);
           }while(getchar() != '\n');
         printf("Telefonnummer..........: ");
         do{
            scanf("%d",&k[counter].telefonnr);
           }while(getchar() != '\n');

         if((fh=open("kunden.dat",O_CREAT|O_RDWR)) == -1)
            printf("Konnte\"kunden.dat\" nicht öffnen\n");
         else if((write(fh,&k,sizeof(k))) == -1)
           printf("Konnte nicht in \"kunden.dat\" schreiben\n");
         else
             counter++;
      }
}

void lese()
{
   int fh;
   int num;

   printf("Bitte geben Sie die Kundennummer ein : ");
   scanf("%d",&num);

   if((fh=open("kunden.dat",O_RDONLY)) == -1)
      {
         printf("Kann Kundendatei nicht öffnen");
         exit (0);
      }
   read(fh,&k,sizeof(k));
   printf("\n\n");
   printf("Name..........%s",k[num].name);
   printf("Vorname.......%s",k[num].vorname);
   printf("Kundennummer..%d\n",k[num].kundenummer);
   printf("Wohnort.......%s",k[num].ort);
   printf("Strasse.......%s",k[num].strasse);
   printf("Hausnummer....%d\n",k[num].hausnummer);
   printf("Vorwahl.......%ld\n",k[num].vorwahl);
   printf("Telefonnum....%ld\n",k[num].telefonnr);
}

int main()
{
   int wahl;

   do{
         printf("\t1: Neuen Kunden eingeben\n\n");
         printf("\t2: Kunden ausgeben\n\n");
         printf("\t3: Programmende\n\n");
         printf("\tEingabe :> ");
         do{
             scanf("%d",&wahl);
           }while(getchar() != '\n');
         switch(wahl)
            {
               case 1 : neukunde(); break;
               case 2 : lese(); break;
               case 3 : printf("bye\n"); break;
               default: printf("Falsche Eingabe!!!\n");
            }
      }while(wahl != 3);
   return 0;
}

Das Datenprogramm ist wieder um eine Funktion reicher geworden, nämlich um lese(). Bei dieser wird mit

if((fh=open("kunden.dat",O_RDONLY)) == -1)  

die Kundendatei zum Lesen geöffnet und mit

read(fh,&k,sizeof(k));

ausgelesen sowie anschließend auf dem Bildschirm ausgegeben.

Ein paar Zeilen noch zum File-Deskriptor. Folgende Anwendung des File-Deskriptors ist bekannt:

write(fh, &puffer, sizeof(puffer));
read(fh, &puffer, sizeof(puffer));

Aber statt des File-Deskriptors fh können logischerweise auch Ganzzahlen verwendet werden. Folgende Ziffern sind allerdings fest belegt, da diese vordefinierte Deskriptoren sind:
Dezimalzahl Bedeutung
0 Standardeingabe (stdin)
1 Standardausgabe (stdout)
2 Standardfehlerausgabe (stderr)

Tabelle 19.17: Besetzte Werte für Deskriptoren

Ein Listing dazu:

#include <stdio.h>
#ifdef __unix__
     #include <unistd.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
     #include <io.h>
#endif

int main()
{
   char puffer[100];
   read(0, &puffer, sizeof(puffer));
   printf("%s",puffer);
   return 0;
}

Mit read(0, &puffer, sizeof(puffer)) wird aus der Standardeingabe (stdin) in die Adresse des Puffers gelesen, also von der Tastatur. Anhand der Ausgabe können Sie auch die Eigenheiten der niedrigeren Ebene erkennen. Hier wird nicht automatisch ein Stringende-Zeichen angehängt, darum müssen Sie sich selbst kümmern. Dasselbe kann auch mit write() auf dem Bildschirm vorgenommen werden:

#include <stdio.h>
#ifdef __unix__
     #include <unistd.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
     #include <io.h>
#endif

int main()
{
   char puffer[] = "Ich werde im Low-Level-I/O ausgegeben";
   write(1, &puffer, sizeof(puffer));
   return 0;
}

Da der File-Deskriptor manchmal so verwendet wird, sollte dies hier nicht unerwähnt bleiben.

19.30. File-Deskriptor positionieren - lseek            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

lseek() ist dieselbe Funktion, die bei der höheren Ebene fseek() hieß, und dient zum Verschieben des File-Deskriptors in der geöffneten Datei. Hier die Syntax von lseek():

#inlcude <unistd.h>      /* für UNIX */
#include <sys/types.h>   /* für UNIX */
#inlcude <io.h>          /* für MS-DOS/WIN */

long lseek(int fh, long offset, int wie);

Die Datei, in welcher der File-Deskriptor verschoben werden soll, wird mit dem File-Deskriptor fh angegeben, der natürlich zuvor mit open() geöffnet bzw. erzeugt wurde. Um wie viele Bytes der File-Deskriptor von der Position wie verschoben werden soll, wird mit offset angegeben. Die Angaben von wie sind dieselben wie schon bei fseek(). Hier nochmals die Tabelle, die zeigt, welche Möglichkeiten zur Verfügung stehen:

wie-Angabe Beschreibung
SEEK_SET oder 0 Schreib-/Lese-Deskriptor vom Dateianfang um offset Bytes versetzen
SEEK_CUR oder 1 Schreib-/Lese-Deskriptor von der aktuellen Position um offset Bytes versetzen
SEEK_END oder 2 Schreib-/Lese-Deskriptor vom Dateiende um offset Bytes versetzen

Tabelle 19.18: Bezugspunkt für die Positionierung (gleich wie bei fseek)

Als Rückgabewert gibt diese Funktion den Wert der aktuellen Position des File-Deskriptors zurück:

long aktuelle_position;
aktuelle_position = lseek(fh, 0L, SEEK_CUR);

Bei Fehler gibt diese Funktion -1 zurück. lseek() sollte allerdings nicht auf kleiner als 0 geprüft werden, sondern auf -1, da es durchaus sein kann, dass es Gerätedateien gibt, die einen negativen Wert zurückliefern. Weitere Möglichkeiten von lseek():

Deskriptor auf den Dateianfang setzen:

lseek(fh, 0L, SEEK_SET);

Deskriptor um 100 Bytes von der aktuellen Position nach vorne versetzen:

lseek(fh, 100L, SEEK_CUR);

Deskriptor um 10 Bytes von der aktuellen Position zurücksetzen:

lseek(fh, -10L, SEEK_CUR);

Deskriptor auf das letzte Byte setzen (nicht EOF):

lseek(fh, -1L, SEEK_END);

Ein Beispiel zu lseek() kann ich mir sparen, da diese Funktion genauso eingesetzt wird wie fseek(); nur, dass anstatt eines Streams hierbei ein File-Deskriptor verwendet wird.

19.31. File-Deskriptor von einem Stream - fileno            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Manchmal benötigen Sie von einem offenen Stream den File-Deskriptor. Die Syntax dieser Funktion lautet:

int fileno(FILE *fz);

fileno() ist erforderlich, falls eine Datei mit fopen() geöffnet wurde, um den Stream für Funktionen einzusetzen, die einen File-Deskriptor benötigen (z.B. Funktionen wie dup(), dup2() oder fcntl()). Hier ein Listing:

#include <stdio.h>
#include <stdlib.h>
#ifdef __unix__
    #include <unistd.h>
#else
    #include <io.h>
#endif

int main()
{
   FILE *fz;
   int fd,fd2;
   char datei[255];

   printf("File-Deskriptoren zu stdin, stdout und stderr : ");
   printf("%d, %d und %d\n"
           ,fileno(stdin),fileno(stdout),fileno(stderr));
   printf("Welche Datei wollen Sie öffnen : ");
   scanf("%s",datei);

   fz=fopen(datei, "r");
   if(!fz)
   {
      perror(NULL);
      exit(0);
   }

   fd = fileno(fz);
   printf("File-Deskriptor zur Datei %s lautet %d\n",datei,fd);

   fd2=dup(fd);

   printf("File-Deskriptor, der kopiert wurde lautet %d\n",fd2);
   return 0;
}

Zu Beginn des Programms werden erst die File-Deskriptoren zu stdin, stdout und stderr ausgegeben, diese sollten immer 0, 1 und 2 sein. Anschließend wird der File-Deskriptor in einer von Ihnen geöffneten Datei ausgegeben. Dieser File-Deskriptor wird jetzt mit der Funktion dup() dupliziert und ebenfalls auf dem Bildschirm ausgegeben.

19.32. Stream von Filehandle - fdopen            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Mit der Funktion fdopen() erhalten Sie aus einem File-Deskriptor einen FILE-Zeiger:

#include <stdio.h>

FILE *fdopen(int fd, const char *modus);

fdopen() ist das Gegenstück zu fileno(). Als modus, wie die Datei geöffnet wird, können dieselben Modi wie bei der Funktion open() genutzt werden.

fdopen() wird oft auf File-Deskriptoren angewandt, die von Funktionen zurückgegeben werden, die Pipes oder Kommunikationskanäle in Netzwerken einrichten. Das kommt daher, weil einige Funktionen (open(), dup(), dup2(), fcntl(), pipe() ...) in Netzwerken nichts mit Streams anfangen können und File-Deskriporen benötigen. Um aber wieder aus Deskriptoren einen Stream (FILE-Zeiger) zu erzeugen, ist die Funktion fdopen() erforderlich. Hierzu ein kurzes Beispiel:

#include <stdio.h>
#ifdef _unix_
   #include <unistd.h>
#else
   #include <io.h>
#endif

int main()
{
   FILE *fz, *fz2;
   int fd,fd2;
   char datei[255];

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

   fz=fopen(datei, "w+");
   if(!fz)
      perror(NULL);
   fd = fileno(fz);
   printf("File-Deskriptor zur Datei %s lautet %d\n",datei,fd);

   fd2=dup(fd);

   printf("Der File-Deskriptor, der kopiert wurde %d\n\n",fd2);

   printf("Wir wollen einen STREAM  oeffnen....\n");
   fz2 = fdopen(fd2, "w");
     if(!fz2)
        perror(NULL);

   fprintf(fz,"Dieser Text steht in %s\n",datei);
   fprintf(fz2,"Dieser Text steht auch in %s\n",datei);
   fprintf(stdout,"Es wurde etwas in die "
                  "Datei %s geschrieben",datei);
   return 0;
}

Die beiden Funktionen fileno() und fdopen() wurden nur kurz behandelt, da diese vorwiegend in der Netzwerkprogrammierung verwendet werden.

Weiter mit Kapitel 20: Attribute von Dateien und Arbeiten mit Verzeichnissen            zum Inhaltsverzeichnis