Themen, wie das ermitteln von Eigenschaften einer Datei oder das öffnen eines Verzeichnisses sind Systemabhänging. Denn jedes Betriebssystem verwaltet Dateien und Verzeichnisse anders. Daher wird oftmals in Büchern auf dieses Thema völlig verzichtet. In diesem Kapitel soll versucht werden, dieses Thema so global wie Möglich zu behandeln. C C++ C/C++ Verzeichnis Verzeichnisse Attribute von Dateien Zugriffsrechte stat fstat Arbeitsverzeichis stat fstat Verzeichnis Verzeichnisse Attribute von Dateien Zugriffsrechte stat fstat Arbeitsverzeichis Kapitel 20: Attribute von Dateien und Arbeiten mit Verzeichnissen

20.3. Verzeichnis-Funktionen            zurück  zum Inhaltsverzeichnis

Bei den bisherigen ANSI C-Funktionen konnte es Ihnen egal sein, wie ein Dateisystem aufgebaut ist. Es gibt zwar einige systemabhängige Faktoren, die zu beachten sind (bspw. das Trennzeichen von Verzeichnisnamen), aber meistens sind diese Funktionen so universell implementiert, dass es dennoch nicht zu Problemen kommt. Bei einem Zugriff auf Verzeichnisse ist es leider nicht mehr so einfach. Hierbei werden meist POSIX-konforme Funktionen verwendet, welche vorwiegend in der UNIX-Welt beheimatet sind. Keine Sorge, auch MS-Windows-Anwender können diese Funktionen nutzen. In viele Compiler unter diesem System sind diese Funktionen integriert.

20.3.1 Verzeichnis erstellen, löschen und wechseln - mkdir, rmdir und chdir

#include <sys/types.h>  /* Linux/UNIX */
#include <sys/stat.h>   /* Linux/UNIX */
#include <dir.h>        /* MS-DOS/WIN */

int mkdir(const char *pfad, [int modus]);

Mit der Funktion mkdir() wird ein neues Verzeichnis mit dem Namen pfad angelegt. Zusätzlich werden in dem neuen Verzeichnis automatisch auch das Arbeitsverzeichnis (Working Directory) (.) und das Eltern-Verzeichnis (Parent Directory) (..) mit angelegt. Die Zugriffsrechte können über modus vergeben werden. Dies gilt aber nur für Linux/UNIX und nicht für Windows/MS-DOS. Die Modi unter Linux/UNIX entnehmen Sie bitte der man-Page von chmod(). Hierzu ein Listing, mit dem ein neues Verzeichnis erstellt wird.

#ifdef __unix__
   #include <sys/types.h>
   #include <sys/stat.h>
   #define MODUS ,0711)
#elif __WIN32__ || _MS_DOS_
    #include <dir.h>
    #define MODUS )
#else
    #include <direct.h>  /* Visual C++ */
    #define MODUS )
#endif
#include <stdio.h>
#include <stdlib.h>

int main()
{
   char pfadname[200];

   printf("Wie soll der neue Ordner heissen: ");
   scanf("%199s",pfadname);
   if(mkdir(pfadname MODUS == -1)/*Nicht schön, aber portabler*/
      printf("Konnte kein neues Verzeichnis erstellen\n");
   else
      printf("Neues Verzeichnis namens %s erstellt\n",pfadname);
   return 0;
}

Wurde das Programm ausgeführt, sollte sich im benannten Verzeichnis ein neuer Ordner mit dem eingegebenen Namen befinden. Unter Linux/UNIX muss außerdem beachtet werden, dass für den modus auch die Ausführrechte (execute-Bits) gesetzt sind, um auch Zugriff auf das neue Verzeichnis zu haben. Als Nächstes soll in das eben erstellte Verzeichnis gewechselt werden. Dies gelingt mit der Funktion chdir(). Die Syntax von chdir():

#include <unistd.h> /* Linux/UNIX  */
#include <dir.h>    /* MS-DOS/WIN */

int chdir(const char *pfad);

Mit chdir() wird in das Arbeitsverzeichnis, welches jedes ablaufende Programm besitzt, gewechselt. Bei einem Fehler gibt diese Funktion -1 zurück, ansonsten 0. In dem folgenden Listing wird erst ein neues Verzeichnis erstellt, danach wird mit chdir() in das erstellte Verzeichnis gewechselt und darin eine Textdatei erzeugt.

#ifdef __unix__
   #include <sys/types.h>
   #include <sys/stat.h>
   #define MODUS ,0711)
#elif _WIN32__ || _MS_DOS_
    #include <dir.h>
    #define MODUS )
#else
    #include <direct.h>
    #define MODUS )
#endif
#include <stdio.h>
#include <stdlib.h>

int main()
{
   char pfadname[200];

   printf("Wie soll der neue Ordner heissen : ");
   scanf("%199s",pfadname);
   if(mkdir(pfadname MODUS == -1)
      printf("Konnte kein neues Verzeichnis erstellen\n");
   else
      printf("Neues Verzeichnis namens %s erstellt\n",pfadname);

   /*Jetzt wollen wir in das neue Verzeichnis wechseln*/
   if(chdir(pfadname) == -1)
      {
         printf("Konnte nicht in das Verzeichnis wechseln\n");
         exit(0);
      }
   else
      printf("Erfolgreich nach %s gewechselt!\n",pfadname);
   /*testfile im Verzeichnis erstellen*/
   fopen("testfile","w");
   return 0;
}

Jetzt sollte sich in dem eben erzeugten Verzeichnis eine Datei namens "testfile" befinden. Es dürfte Ihnen aufgefallen sein, dass wenn sich das Programm beendet, es automatisch wieder in das Verzeichnis des Elternprozesses zurückwechselt.

Wenn Sie mehrmals in einem Programm Verzeichnisse erstellen müssen und in diese wechseln, schreiben Sie besser eine Funktion wie z.B.:

int makedir(char *dir)
{
   if(mkdir(dir,0755) != -1) /*Windos/MS-DOS ohne 0755*/
      if(chdir(dir) != -1)
         return OK;
   return ERROR;
}

Wollen Sie ein Verzeichnis wieder löschen, können Sie die Funktion rmdir() verwenden. Die Syntax von rmdir() lautet:

#include <unistd.h>   /* UNIX/Linux */
#include <dir.h>      /* MS-DOS */

int rmdir(const char *pfad);

Mit rmdir() kann ein Verzeichnis (rmdir = remove directory) gelöscht werden. Unter Linux/UNIX setzt dies allerdings voraus, dass dieses Verzeichnis außer dem (.) und (..) keinen anderen Eintrag mehr beinhaltet. Bei Erfolg gibt diese Funktion 0 und bei einem Fehler -1 zurück.

Dazu soll das Programm, welches eben verwendet wurde, erweitert werden. Das Verzeichnis, welches erstellt, in das gewechselt und in dem eine Datei erzeugt wurde, soll am Ende des Programms wieder gelöscht werden. Hier das Listing:

#ifdef __unix__
   #include <sys/types.h>
   #include <sys/stat.h>
   #define MODUS ,0711)
#elif _WIN32__ || _MS_DOS_
    #include <dir.h>
    #define MODUS )
#else
    #include <direct.h>
    #define MODUS )
#endif
#include <stdio.h>
#include <stdlib.h>

int makedir(char *dir)
{
   if(mkdir(dir MODUS != -1)
      if(chdir(dir) != -1)
         return 0;
   return -1;
}

int main()
{
   char pfadname[200];

   printf("Wie soll der neue Ordner heissen : ");
   scanf("%199s",pfadname);

   if(makedir(pfadname) == -1)
      {
         printf("Konnte kein neues Verzeichnis erstellen\n");
         exit(0);
      }
   /*testfile im Verzeichnis erstellen*/
   fopen("testfile","w");

   if(rmdir(pfadname) == -1)
      printf("Konnte Verzeichnis %s nicht löschen!!\n",pfadname);
   return 0;
}

Unter MS-DOS/Windows wird das Listing problemlos funktionieren. Mit Linux/UNIX kann das Verzeichnis nicht gelöscht werden, da sich dort noch eine Datei befindet. Das Verzeichnis muss also zuvor leer sein. Das vollständige Verzeichnis lässt sich mit folgendem Shellaufruf leeren:

rmdir Verzeichnis | rm -rf Verzeichnis 

Im Listing kann dieser Aufruf folgendermaßen eingesetzt werden:

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
   char pfadname[200];
   char deletefiles[200];
   FILE *tmp;
   printf("Welchen Ordner wollen Sie löschen : ");
   scanf("%189s",pfadname);
   strcpy(deletefiles,"rm -rf ");
   strcat(deletefiles,pfadname);
   strcat(deletefiles,"/*");
   printf("%s\n",deletefiles);
   system(deletefiles);
   if(rmdir(pfadname) == -1)
      printf("Konnte Verzeichnis %s nicht löschen!!\n",pfadname);
   return 0;
}

20.3.2 Wechseln in das Arbeitsverzeichnis - getcwd
Mit der Funktion getcwd() lässt sich der Name des Arbeitsverzeichnisses (Working Directory) ermitteln. Hier die Syntax von getcwd():

#include <unistd.h>    /* Linux/UNIX */
#include <dir.h>       /* MS-DOS/WIN */

char *getcwd(char *puffer, int puffergrösse);

Die Funktion schreibt in die Speicheradresse puffer den Pfadnamen des Arbeitsverzeichnisses mit abschließendem '\0'. Mit puffergrösse wird die Größe des Puffers angegeben. Die Funktion gibt bei Erfolg den Pfadnamen des Arbeitsverzeichnisses an puffer zurück oder bei einem Fehler NULL. Hier ein Beispiel, wie diese Funktion verwendet wird:

#ifdef __unix__
    #include <unistd.h>
#elif __WIN32__ || _MS_DOS_
    #include <dir.h>
#else
    #include <direct.h> /* Visual C++ */
#endif
#include <stdio.h>
#include <stdlib.h>

int main()
{
   char puffer[200];
   if(getcwd(puffer,sizeof(puffer)) == NULL)
      {
         fprintf(stderr,"Fehler bei getcwd...\n");
         exit (0);
      }
   printf("Working-Directory: %s\n",puffer);
   return 0;
}

Für Linux/UNIX gilt außerdem: Wechseln Sie in ein Verzeichnis, welches ein symbolischer Link auf ein anderes Verzeichnis ist, so wird in das Verzeichnis gewechselt, auf das der symbolische Link zeigt.

Ein praktisches Beispiel unter Linux: Der User hat den Namen seines Home-Verzeichnis vergessen. Er muss aber jetzt wieder in das Verzeichnis wechseln. Welches das ist, kann er herausfinden mit der Eingabe des Shellbefehls env (Environment) oder mit der C-Funktion getenv(). Hier das Listing:

#ifdef __unix__
    #include <unistd.h>
#elif __WIN32__ || _MS_DOS_
    #include <dir.h>
#else
    #include <direct.h> /* Visual C++ */
#endif
#include <stdio.h>
#include <stdlib.h>

int main()
{
   char puffer[200];
   char home[200];

   /*Das Heimatverzeichnis nach home*/
   strncpy(home,getenv("HOME"),199);
   /*Working Directory lesen*/
   if(getcwd(puffer,sizeof(puffer)) == NULL)
      {
         fprintf(stderr,"Fehler bei getcwd...\n");
         exit (1);
      }
   /*Sind wir schon im Heimatverzeichnis?*/
   if(strcmp(home,puffer) == 0)
      printf("Wir sind daheim : %s\n",puffer);
   else
      { /*Nicht! Dann wechseln wir in Heimatverzeichnis*/
         chdir(home);
         /*Der Beweis*/
         printf("back at home: %s \n",
                 getcwd(puffer,sizeof(puffer)));
      }
   return 0;
}

20.3.3 Verzeichnisse öffnen, lesen und schließen - opendir, readdir und closedir
Um Verzeichnisse zu lesen, ist in der Headerdatei #include <dirent.h> eine interne Struktur namens DIR deklariert. Der Inhalt dieser Struktur ist hier jetzt nicht von Interesse, sondern die folgenden Funktionen, die mit der Struktur arbeiten.

Hinweis: Die folgenden Funktionen sind leider nicht mit dem Microsoft Visual C++ Compiler ausführbar. Dafür wird aber am Ende des Kapitels ein extra Listing angefertigt, das zeigt, wie auch mit dem Visual C++ Compiler Programme erstellt werden können, die ein Verzeichnis auslesen.

Tipp
 

Wollen Sie die folgenden Beispiele mit der kostenlosen Entwicklungsumgebung Bloodshed Dev-C++ durchführen, müssen Sie im Menü über Projekt · Projektoptionen in der Liste Linker die Bibliothek -lmingwex eintragen. Eventuell kann dies aber auch über das Menü Werkzeuge · Compiler · Optionen in die Liste Linker eingetragen werden. Hierfür genügt aber dann folgender Eintrag: mingwex

opendir() - ein Verzeichnis öffnen
Zuerst die Funktion opendir():

#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *dirname);

Bei Erfolg wird mit dieser Funktion das Verzeichnis dirname geöffnet, auf dessen Adresse dann der DIR-Zeiger verweist. Ansonsten wird bei einem Fehler NULL zurückgegeben.

Der DIR-Zeiger wird jetzt verwendet, um den Inhalt eines Verzeichnisses auszulesen. Dies wird jetzt gleich mit der Funktion readdir() vorgenommen.

readdir() - aus einem Verzeichnis lesen
Hier die Syntax:

#include <sys/types.h>
#include <dirent.h>

struct dirent *readdir(DIR *dir);

Bei einem Fehler gibt diese Funktion ebenfalls NULL zurück. Ansonsten eine Adresse der Struktur dirent, welche Folgendes beinhaltet:

struct dirent {
       long d_ino; /*i-node Nr. (bei Windows/MS-DOS immer 0)*/
       unsigned short d_reclen; /* (bei Windows/MS-DOS immer 0)*/
       unsigned short d_namlen; /* Länge des Namens in d_name */
       char *d_name;             /*Dateiname mit abschl. '\0'*/
              };

In der Praxis kann die Funktion readdir() so verwendet werden:

DIR *dir;
struct dirent *dirzeiger;
/* Verzeichnis öffnen */
if((dir=opendir(dirname)) != NULL)
/*komplettes Verzeichnis Eintrag für Eintrag auslesen*/
while((dirzeiger=readdir(dir)) != NULL)
    printf("%s\n",(*dirzeiger).d_name);

Es wird zuerst mit opendir() ein Verzeichnis geöffnet und danach mit readdir() der komplette Inhalt des Verzeichnisses ausgegeben.

rewinddir() - Verzeichnis-Zeiger zurücksetzen auf den Anfang
Mit der Funktion rewinddir() wird der Lesezeiger wieder an den Anfang der Namensliste des Verzeichnisses zurückgesetzt. Die Syntax von rewinddir():

#include <sys/types.h>
#include <dirent.h>

void rewinddir(DIR *dir);

closedir() - Verzeichnis schließen
Am Ende wird dann mit der Funktion closedir() das Verzeichnis geschlossen, welches mit opendir() geöffnet wurde. Bei Erfolg gibt diese Funktion 0 und bei Fehler -1 zurück. Die Syntax lautet:

#include <sys/types.h>
#inlcude <dirent.h>

int closedir(DIR *dir);

Es folgt ein ausführbares Beispiel, welches alle Funktionen in Aktion demonstriert:

#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
   DIR *dir;
   struct dirent *dirzeiger;

   if(argc != 2)
      {
         fprintf(stderr,"Benutzung : %s Directory\n",argv[0]);
         exit (1);
      }
   /* Das Verzeichnis öffnen */
   if((dir=opendir(argv[1])) == NULL)
      {
         fprintf(stderr,"Fehler bei opendir.....\n");
         exit (0);
      }
   /* Das komplette Verzeichnis auslesen */
   while((dirzeiger=readdir(dir)) != NULL)
      printf("%s\n",(*dirzeiger).d_name);
   /* Lesezeiger wieder schliessen */
   if(closedir(dir) == -1)
      printf("Fehler beim Schliessen von %s\n",argv[1]);
   return 0;
}

Mit diesem Programm wird das vollständige Verzeichnis ausgegeben, welches Sie über die Kommandozeile angeben.

telldir() und seekdir() - Positionierung im Verzeichnis
Auf einigen Systemen gibt es zusätzlich noch die Funktion telldir():

#include<sys/types.h>
#include<sys/dirent.h>

off_t telldir(DIR *dirptr)

Diese Funktion liefert zu einem mit readdir() gelesenen Verzeichnis die Position des Lesezeigers zurück.

Mit der Funktion seekdir() lässt sich die Position des DIR-Zeigers verschieben:

#include<sys/types.h>
#include<sys/dirent.h>

void seekdir(DIR *dirptr, off_t pos)

Damit wird der mit opendir() geöffnete Lesezeiger (dirptr) auf die Position pos gesetzt, welche Sie zuvor mit der Funktion telldir() ermittelt haben. Hierzu noch ein kurzer Ausschnitt dieser beiden Funktionen:

off_t pos;
/* aktuelle Position im Verzeichnis ermitteln */
pos = telldir(dir_ptr);
/* viele Funktionen */
…
/* zur aktuellen Position zurückspringen */
seekdir(dir_ptr, pos);

Probleme mit der Portabilität
Ein Problem bei den Funktionen wie opendir(), readdir() oder closedir() ist, dass diese Funktionen POSIX-konform und aus diesem Grund häufig nicht bei Compilern für MS-Windows implementiert sind. Unter UNIX-artigen Systemen müssen Sie sich wegen dieser Funktionen keine Gedanken machen. Um also unter MS-Windows, genauer unter WIN32, ein vollständiges Verzeichnis auszugeben, müssen Sie auf die Windows-Systemprogrammierung zurückgreifen. Die Windows-Programmierung hier genauer zu erläutern, würde den Rahmen des Kapitels, gar des Buchs, sprengen. Aber zu Anschauungszwecken folgt hier eine portablere Lösung, mit der Sie ein vollständiges Verzeichnis ausgeben lassen können.

#include <stdio.h>

#ifdef __unix__
#include <dirent.h>
#include <sys/types.h>
/* UNIX-Funktion zum Ausgeben des kompl. Verzeichnisses */
void list_dir(const char *path)
{
   DIR *dirptr;
   struct dirent *dir;

   if ((dirptr=opendir(path)) == NULL)
      return;
   while((dir=readdir(dirptr)) != NULL)
      printf("%s\n",dir->d_name);
   closedir(dirptr);
}

#elif __WIN32__ || _MSC_VER
#include <windows.h>
/* Win32-Funktion zum Ausgeben des kompl. Verzeichnisses */
void list_dir(const char *path)
{
   WIN32_FIND_DATA dir;
   HANDLE fhandle;
   char directory[256];
   /* Unsicher, besser wäre falls vorhanden snprintf() */
   sprintf(directory,"%s\\*.*",path);
   /* Handle auf das Verzeichnis director */
   if ((fhandle=FindFirstFile(directory,&dir)) !=
                             INVALID_HANDLE_VALUE)
      {
         do {/* Verzeichnis auslesen */
               printf("%s\n", dir.cFileName);
            } while(FindNextFile(fhandle,&dir));
      }
   FindClose(fhandle);
}
#endif

int main(int argc,char **argv)
{
   if (argc < 2)
      list_dir(".");
   else
      list_dir(argv[1]);
   return 0;
}

Bei der Win32-Funktion wurden hier die MS-DOS-ähnlichen Funktionen findfirst() und findnext() verwendet. Die Funktion FindFirstFile() gibt einen Filehandle auf die erste Datei im Verzeichnis zurück. Während FindNextFile() den Handle immer um eine Position weitersetzt, bis keine Dateien mehr im Verzeichnis zum Lesen vorhanden sind. FindClose() schließt den Filehandle wieder.

Weiter mit Kapitel 21: Arbeiten mit variablen langen Argumentlisten <stdarg.h>            zum Inhaltsverzeichnis