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.19. Datei löschen oder umbenennen - remove und rename            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Manchmal ist es erforderlich, eine Datei zu löschen bzw. diese umzubenennen.

19.19.1 remove()
Zum Löschen einer Datei kann die Funktion remove() verwendet werden. Hier die Syntax:

#include <stdio.h>

int remove(const char *pfadname);

Bei Erfolg gibt diese Funktion 0 und bei Misserfolg -1 zurück. Voraussetzungen für ein erfolgreiches Löschen sind außerdem der richtige Pfad und auch entsprechende Zugriffsrechte auf die Datei. Ein kurzes Beispiel zu remove():

#include <stdio.h>

int main()
{
   char pfad[100];
   printf("Welche Datei wollen Sie löschen?\n");
   printf("Bitte wenn nötig gültigen Pfad angeben.\n");
   printf("Eingabe :> ");
   scanf("%99s",pfad);

   if((remove(pfad)) <0)
      fprintf(stderr,"Fehler beim Löschen von %s",pfad);
   return 0;
}

Hinweis
 

Eine kurze Anmerkung bezüglich des absoluten Löschens einer Datei. Wenn eine Datei mit remove() gelöscht wird, geben Sie nur den Speicherplatz frei; also sämtliche Einträge, die zu einer Datei gehören. Aber auf den einzelnen Adressen befinden sich immer noch die Daten, und die lassen sich oftmals wiederherstellen.

Wollen Sie sichergehen, dass eine Datei nicht mehr wiederhergestellt werden kann, empfiehlt es sich, den Inhalt der Datei zuerst zu "zerstören", ehe Sie mit remove() die Datei löschen. Der einfachste Weg ist es, den ganzen Inhalt mit irgendwelchen Zeichen zu beschreiben. Im folgenden Beispiel wurde dies mit fwrite() und dem Stringende-Zeichen '\0' durchgeführt:

#include <stdio.h>

void my_remove(char *rm)
{
   unsigned long size;
   FILE *rem=fopen(rm, "w+b");
   if(rem != NULL)
      {
         fseek(rem, 0L, SEEK_END);  /* Stream an das Dateiende */
         size=ftell(rem);         /* Grösse in Bytes ermitteln */
         /* Kompletten Inhalt mit \0 überschreiben */
         fwrite((char *)'\0', 1, size, rem);
         /* damit die Datei gelöscht werden kann, schliessen */
         fclose(rem);
         remove(rm);    /* Jetzt weg damit */
      }
}

int main()
{
   char datei[] = "test.txt";
   my_remove(datei);
   return 0;
}

Jetzt ist es zwar einigen Experten noch möglich, die Datei wiederherzustellen, doch am Inhalt der Datei dürften sich auch sie die Zähne ausbeißen.

19.19.2 rename()
Mit der Funktion rename() kann der Name einer Datei umbenannt werden. Die Syntax dafür lautet:

#include <stdio.h>

int rename(const char *altname, const char *neuname);

Wenn alles richtig verlief, gibt die Funktion 0, ansonsten bei Fehler -1 zurück. Hier ein kurzes Listing, wie rename() angewendet wird:

#include <stdio.h>

int main()
{
   char alt[20],neu[20];

   printf("Welche Datei wollen Sie umbenennen : ");
   scanf("%19s",alt);
   printf("Wie soll die Datei heissen: ");
   scanf("%19s",neu);

   if((rename(alt,neu)) <0)
      fprintf(stderr,"Fehler beim Umbenennen von %s",alt);
   return 0;
}

Sollte eine Datei umbenannt werden und einen Namen erhalten, der bereits vorhanden ist, ist das Verhalten systemabhängig.

Die Funktionen rename() und remove() funktionieren unter UNIX/Linux-Systemen aber nur, wenn Sie ausreichende Zugriffsrechte für diese Datei besitzen. Unter Windows/MS-DOS ist dies teilweise noch zweitrangig.

19.20. Pufferung einstellen - setbuf und setvbuf            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Mit der Funktion setbuf() wird einer Datei ein Dateipuffer zugeordnet. Die Syntax lautet:

#include <stdio.h>

void setbuf(FILE *datei, char *puffer);

Der geöffnete Stream datei erhält durch setbuf() den Dateipuffer puffer. Dieser muss die Länge BUFSIZ haben. Die symbolische Konstante BUFSIZ befindet sich in der Headerdatei <stdio.h>. Wie groß BUFSIZ auf Ihrem System ist, kann mit folgendem Listing ermittelt werden:

#include <stdio.h>

int main()
{
   printf("Die max. Groesse des Puffers: %d\n",BUFSIZ);
   return 0;
}

Der Wert für BUFSIZ dürfte in der Regel 256 KB, 512 KB oder auch 4096 KB betragen. Dies ist abhängig vom System und vom Compiler.

Geben Sie hingegen für puffer den NULL-Zeiger an, erfolgt die Datenübertragung ungepuffert. Das würde Zeichen für Zeichen bedeuten und natürlich erheblich mehr Zeit beanspruchen, da jedes einzelne Zeichen gelesen und anschließend wieder geschrieben wird.

Sie müssen die Funktion setbuf() unmittelbar nach dem Öffnen einer Datei aufrufen, noch vor einer Lese- oder Schreiboperation.

Zur Demonstration folgt ein Listing, welches gepuffertes und ungepuffertes Kopieren von Daten vergleicht. Außerdem werden dabei die Funktionen getc() und putc() zum Lesen und Schreiben verwendet, welche zwar zeichenweise arbeiten, aber dennoch vom Puffer abhängig sind. Hier das Listing:

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

#define DATEIGROESSE 10000000L
#define DATEI1 "test.1"
#define DATEI2 "test.2"

void copy1(char *quelle, char *ziel)
{
   FILE *q,*z;
   int c;
   time_t t1 = time(NULL);
   printf("Kopiere zeichenweise mit getc() und putc()\n");
   q=fopen(quelle,"rb");
   if( q != NULL)
      {
         z=fopen(ziel,"wb");
         if(NULL == z)
            {
               fprintf(stderr,"Fehler beim Öffnen (%s)\n",ziel);
               exit(0);
            }
      }
   else
      {
         fprintf(stderr,"Fehler beim Öffnen von %s\n",quelle);
         exit(0);
      }
   while((c=getc(q)) != EOF)
      putc(c,z);
   fclose(q);
   fclose(z);
   printf("Zeit = %d sec.\n",time(NULL)-t1);
}

void copy2(char *quelle, char *ziel)
{
   FILE *q,*z;
   static char puffer1[BUFSIZ];
   static char puffer2[BUFSIZ];
   int c;
   time_t t1 = time(NULL);

   printf("Gepuffertes Kopieren mit setbuf(stream,BUFSIZE)\n");
   q=fopen(quelle,"rb");

   if(q != NULL)
      {
         z=fopen(ziel,"wb");
         if(NULL == z)
            {
               fprintf(stderr,"Fehler beim Öffnen (%s)\n",ziel);
               exit (0);
            }
      }
   else
      {
         fprintf(stderr,"Fehler beim Öffnen von %s\n",quelle);
         exit(0);
      }

   setbuf(q,puffer1);
   setbuf(z,puffer2);

   while((c=getc(q)) != EOF)
      putc(c,z);

   fclose(q);
   fclose(z);
   printf("Zeit = %d sec.\n",time(NULL)-t1);
}

void copy3(char *quelle, char *ziel)
{
   FILE *q,*z;
   int c;
   time_t t1 = time(NULL);

   printf("Ungepuffertes Kopieren mit setbuf(stream, NULL)\n");
   q=fopen(quelle,"rb");
   if(q != NULL)
      {
         z=fopen(ziel,"wb");
         if(NULL == z)
            {
               fprintf(stderr,"Fehler beim Öffnen (%s)\n",ziel);
               exit(0);
            }
      }
   else
      {
         fprintf(stderr,"Fehler beim Öffnen von %s\n",quelle);
         exit (1);
      }

   setbuf(q,NULL);
   setbuf(z,NULL);

   while((c=getc(q)) != EOF)
      putc(c,z);

   fclose(q);
   fclose(z);
   printf("Zeit = %d sec.\n",time(NULL)-t1);
}

void erzeuge_datei()
{
   FILE *create;
   create=fopen(DATEI1,"wb");
   if(NULL == create)
      {
         fprintf(stderr,"Konnte keine Datei erzeugen\n");
         exit(0);
      }
   fseek(create,DATEIGROESSE-1,SEEK_SET);
   putc('x',create);
   fclose(create);
}

int main()
{
   printf("Datei %s wird erzeugt\n",DATEI1);
   erzeuge_datei();
   copy1(DATEI1,DATEI2);
   copy2(DATEI1,DATEI2);
   copy3(DATEI1,DATEI2);

   remove(DATEI1);
   remove(DATEI2);
   return 0;
}

Abbildung 19.8: Zeitvergleiche mit gepufferter und ungepufferter Einstellung
Abbildung 19.8: Zeitvergleiche mit gepufferter und ungepufferter Einstellung

Zuerst wird eine Datei von zehn Megabyte Größe mit der Funktion erzeuge_datei() angelegt. Anschließend wird die erzeugte Datei "test.1" in die Datei "test.2" kopiert, ohne die Funktion setbuf() zu verwenden (Funktion copy1()).

Als Nächstes wird die Funktion copy2() verwendet, bei der zum ersten Mal setbuf() eingesetzt wird. Als Pufferungsgröße wird hierbei die Konstante BUFSIZ verwendet. Der Zeitverbrauch ist wieder derselbe wie zuvor ohne setbuf(). Also können Sie sich setbuf() mit der Größe von BUFSIZ ersparen, da dies die Standardeinstellung für die Funktionen getc() und putc() zu sein scheint.

Als Letztes wurde die Funktion copy3() ausgeführt, bei der der Puffer auf NULL gesetzt wird. Somit wird ungepuffert kopiert. Das dauert natürlich eine Weile, da nach jedem Lesezugriff pro Byte gleich wieder ein Schreibzugriff erfolgt.

Am Ende werden diese beiden Dateien mittels remove() wieder gelöscht, damit nicht unnötig Datenmüll auf der Platte übrig bleibt.

Hinweis
 

Die Geschwindigkeit des Kopiervorgangs - wie im Listing demonstriert - ist nicht nur von der Power des Rechners abhängig. Einen sehr bedeutenden Anteil daran hat auch der Compiler selbst. Obiges Programm wurde mit einem anderen Compiler übersetzt und lief bis zu dreimal schneller.



Hinweis
 

Die Funktion setbuf() ist mittlerweile veraltet und wird nur noch aus Kompatibilitätsgründen erhalten. Es empfiehlt sich, die neuere Funktion setvbuf() zur Veränderung des Dateipuffers zu verwenden.

Zur Puffereinstellung kann aber auch die Funktion setvbuf() eingesetzt werden, welche ähnlich funktioniert wie setbuf(). Hierzu lautet die Syntax:

#include <stdio.h>

int setvbuf(FILE *datei,char *puffer,int modus,
            size_t puffergroesse);

Wenn alles in Ordnung ging, liefert diese Funktion 0 zurück, andernfalls einen Wert ungleich 0. Die ersten beiden Parameter (FILE *datei,char *puffer) haben dieselbe Bedeutung wie schon bei der Funktion setbuf(). Zusätzlich stehen hier für den Parameter modus drei symbolische Konstanten zur Verfügung:
Puffertyp (modus) Bedeutung
_IOLBF Datei wird zeilenweise gepuffert
_IONBF Ein-/Ausgabe wird gar nicht gepuffert
_IOFBF Ein-/Ausgabe wird voll gepuffert

Tabelle 19.8: Konstanten für die Einstellung des Puffers mit setvbuf()

Falls hierbei für puffer NULL angegeben wird, alloziert die Funktion einen eigenen Speicher der Größe puffergrösse. Das hört sich komplexer an als es ist. setbuf() ohne Pufferung verwenden Sie bspw. so:

setbuf(quelle,NULL); 

Hiermit wurde für den Stream quelle die Pufferung abgeschaltet (ungepuffert). Mit setvbuf() würde dies so erreicht:

setvbuf(quelle, NULL, _IONBF, BUFSIZ); 

Für den Stream quelle wurde der Puffer nun ebenso abgeschaltet.

Wenn Sie die Pufferung auf z.B. 50 KB einstellen wollen, um Daten vom Stream quelle zum Stream ziel zu kopieren, so ergeben sich bei setvbuf() folgende Argumente:

setvbuf(quelle, NULL, _IOFBF, 50000L);
setvbuf(ziel, NULL, _IOFBF, 50000L);

Für eine zeilenweise Pufferung könnten Sie folgende Angaben machen:

setvbuf(quelle, NULL, _IOLBF, 80);
setvbuf(ziel, NULL, _IOLBF, 80);

So werden von quelle nach ziel mindestens 80 Zeichen kopiert, oder es wird bis zum nächsten Newline-Zeichen (\n) kopiert.

Sie haben auf diese Weise mit der Funktion setvbuf() die Möglichkeit, einen Dateipuffer bestimmter Länge zuzuordnen.

19.21. Temporäre Dateien erzeugen - tmpfile und tmpnam            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Wollen Sie Daten während der Laufzeit eines Programms temporär zwischenspeichern, müssen Sie nicht extra eine neue Datei erstellen. Dafür gibt es in C spezielle Funktionen.

Mit der Funktion tmpnam() kann ein eindeutiger Name für eine temporäre Datei erzeugt werden. Die Syntax sieht wie folgt aus:

#include <stdio.h>

char *tmpnam(char *zgr);

Ein eindeutiger Name heißt, dass es sich um keinen Namen einer bereits existierenden Datei handelt. Insgesamt können TMP_MAX eindeutige Namen erzeugt werden. TMP_MAX ist in der Headerdatei <stdio.h> deklariert. Mit folgendem Programm können Sie herausfinden, wie viele temporäre Dateien auf Ihrem System erzeugt werden können:

#include <stdio.h>

int main()
{
   printf("TMP_MAX = %u\n",TMP_MAX);
   return 0;
}

Wird die Funktion tmpnam() hingegen so verwendet

tmpnam(NULL); 

wird die Datei in einem static-Speicherbereich untergebracht, und die Adresse wird als Funktionswert wiedergegeben. Das bedeutet, dass nachfolgende Aufrufe der Funktion tmpnam() dieselbe Adresse einnehmen. Aus diesem Grund sollte zuerst umkopiert werden, um die alte Adresse nicht zu überschreiben.

Sollte tmpnam() ohne den NULL-Zeiger aufgerufen werden, wird für zgr ein Adressbereich adressiert, der L_tmpnam Zeichen aufnehmen kann. L_tmpnam ist ebenso in der Headerdatei <stdio.h> deklariert.

#include <stdio.h>

int main()
{
   printf("L_tmpnam= %d Zeichen\n",L_tmpnam);
   return 0;
}

Hinweis
 

Beim Übersetzen mit Linux/UNIX bekommen Sie bei tmpnam() eine Warnung zurück, diese Funktion nicht zu verwenden. Es wird Ihnen geraten, die Funktion mktemp() einzusetzen. Diese Warnung wird deshalb ausgegeben, weil die Funktion tmpnam() einen eindeutigen Namen zurückgibt. In der Zeit zwischen der Erzeugung des Namens und dem Öffnen der Datei könnte theoretisch eine Datei mit demselben Namen untergeschoben werden. Mehr dazu können Sie in den entsprechenden man-Pages nachlesen.

Jetzt zur Funktion tmpfile(). Die Syntax lautet:

#include <stdio.h>

FILE *tmpfile(void);

Mit tmpfile() wird eine temporäre Datei im Binärmodus zum Lesen und Schreiben geöffnet. Bei ordnungsgemäßer Beendigung des Programms wird diese automatisch wieder gelöscht.

Jetzt ist es Zeit, diese Funktionen anhand eines Programmbeispiels zu demonstrieren:
#include <stdio.h>

char string[] = {
                  "Dies ist der String für die Testdatei\n"
                  "Diese Zeile ist ein Lückenfüller !!!!\n"
                  "Aber diese Zeile soll verändert werden\n"
                  "Hier könnten noch viele Zeilen mehr stehen\n"
                };

void create_text_file(void)
{
   FILE *out;
   out = fopen("test.txt", "w");
   if(NULL == out) exit(0);
   fputs(string, out);
   fclose(out);
}

int main()
{
   FILE *in, *out;
   char line[80], *tmp;
   int line_nr=1;
   /* Es wird eine Datei mit dem Inhalt string erzeugt */
   create_text_file();
   /* Die Datei zum Ändern wird zum Lesen geöffnet */
   in  = fopen("test.txt", "r");
   if(NULL == in)
      {
         fprintf(stderr, "Konnte Datei nicht erzeugen!\n");
         exit(0);
      }
   tmp = tmpnam(NULL);     /* Temporäre Datei erzeugen */
   /* Temporäre Datei zum Schreiben öffnen */
   out = fopen(tmp, "w");
   if(NULL == out)
      {
         fprintf(stderr, "Konnte Datei nicht öffnen!\n");
         exit(0);
      }
   /* Aus der Datei zeilenweise lesen und in
      temporäre Datei schreiben */
   while( NULL != fgets(line, 80, in))
      {
         /* Es soll die dritte Zeile geändert werden */
         if(line_nr == 3)
            fputs("Ein veränderte Zeile!\n", out);
         else
            fputs(line, out);
         line_nr++;
      }
   fclose(in);
   fclose(out);

   remove("test.txt"); /* Original löschen */
   rename(tmp, "test.txt");  /* Temporäre Datei umbenennen */
   return 0;
}

Das Programm demonstriert den Einsatz temporärer Dateien recht eindrücklich. Es wird zuerst eine Textdatei "test.txt" erzeugt, welche mit dem Inhalt von string beschrieben wird. Diese Datei wird jetzt zum Lesen geöffnet. Anschließend wird mit tmpnam() eine temporäre Datei erzeugt. Durch das Argument NULL befindet sich diese Datei im static-Speicherbereich. Diese temporäre Datei öffnen Sie nun zum Schreiben. In der while()-Schleife wird Zeile für Zeile aus der Datei gelesen und in die temporäre Datei geschrieben. Ist die dritte Zeile erreicht, wird diese geändert. Danach geht es wieder Zeile für Zeile weiter, bis keine mehr vorhanden ist. Am Ende des Programms wird die Originaldatei gelöscht und die temporäre Datei in diese umbenannt. Das Listing stellt eine einfache Möglichkeit dar, eine Textdatei zu verändern. Dies kann z.B. recht sinnvoll sein, wenn Sie ein Textverarbeitungsprogramm entwickeln wollen und dabei eine Funktion einbauen, die alle 10 Minuten eine Sicherungskopie vom Originaltext erstellt. Die meisten guten Textverarbeitungsprogramme besitzen dieses Feature. Jetzt will ich Ihnen auch noch ein Listing mit der Funktion tmpfile() zeigen:

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

int main()
{
   FILE *tmp;
   int i;
   char tempdatei[L_tmpnam], zeile[1000];

   printf("Demo Funktion tmpnam.......\n");
   for(i=1; i<=4; i++)
      {
         if(i%2==0)
            {
               printf("%20d. %s\n",i,tmpnam(NULL));
            }
         else
            {
               tmpnam(tempdatei);
               printf("%20d. %s\n",i,tempdatei);
            }
      }

   printf("Demo Funktion tmpfile........\n");
   tmp=tmpfile();
   if( NULL == tmp )
      {
         fprintf(stderr,"Fehler bei tmpfile\n");
         exit (0);
      }

   /*Wir schreiben mit fputs in die temporäre
     Datei, auf die tmp zeigt*/
   fputs("Dieser Text wird in die temporäre"
         " Datei geschrieben\n",tmp);
   rewind(tmp);

   if(fgets(zeile,sizeof(zeile),tmp) == NULL)
      {
         fprintf(stderr,"Fehler bei fgets.....\n");
         exit (1);
      }
   printf("%s\n",zeile);
   return 0;
}

Als Erstes wird in diesem Programm die Funktion tmpnam() demonstriert. Sie erzeugen zwei temporäre Dateien mit dem NULL-Zeiger und zwei ohne. Anschließend erstellen Sie eine temporäre Datei und schreiben mit fputs() einen Text in diese. Danach lesen Sie aus der angefertigten temporären Datei, auf die der FILE-Zeiger tmp zeigt. Bei ordentlichem Beenden wird diese temporäre Datei auch wieder gelöscht. Zum Beweis, dass auch tatsächlich eine temporäre Datei erzeugt wurde, können Sie vor der Zeile

if(fgets(zeile,sizeof(zeile),tmp) == NULL)

aus der temporären Datei lesen und das Programm mit exit() abbrechen. Etwa so:

rewind(tmp);
exit (1);
if(fgets(zeile,sizeof(zeile),tmp) == NULL)

Nun sollte sich im Verzeichnis, in dem Sie das Programm ausführen, eine Datei befinden mit folgendem Textinhalt:

Dieser Text wird in die temporäre Datei geschrieben 

Weiter mit 19.22. Fehlerausgabe mit strerror und perror            zum Inhaltsverzeichnis