Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 2 E/A-Funktionen
  gp 2.1 Elementare E/A-Funktionen
  gp 2.2 Filedeskriptor
    gp 2.2.1 Verwaltung für offene Deskriptoren
  gp 2.3 Funktionen, die den Filedeskriptor verwenden
    gp 2.3.1 Datei öffnen – open()
    gp 2.3.2 Anlegen einer neuen Datei – creat()
    gp 2.3.3 Datei schließen – close()
    gp 2.3.4 Schreiben von Dateien – write()
    gp 2.3.5 Lesen von Dateien – read()
    gp 2.3.6 Schreib-/Lesezeiger positionieren – lseek()
    gp 2.3.7 Duplizieren von Filedeskriptoren – dup() und dup2()
    gp 2.3.8 Ändern oder Abfragen der Eigenschaften eines Filedeskriptors – fcntl()
    gp 2.3.9 Record Locking – Sperren von Dateien einrichten
    gp 2.3.10 Multiplexing E/A – select()
    gp 2.3.11 Unterschiedliche Operationen – ioctl()
    gp 2.3.12 Lesen und Schreiben mehrerer Puffer – writev() und readv()
    gp 2.3.13 Übersicht zu weiteren Funktionen, die den Filedeskriptor verwenden
  gp 2.4 Standard-E/A-Funktionen
    gp 2.4.1 Der FILE-Zeiger
    gp 2.4.2 Öffnen und Schließen von Dateien
    gp 2.4.3 Formatierte Ausgabe
    gp 2.4.4 Formatierte Eingabe
    gp 2.4.5 Binäres Lesen und Schreiben
    gp 2.4.6 Zeichen- und zeilenweise Ein-/Ausgabe
    gp 2.4.7 Status der Ein-/Ausgabe überprüfen
    gp 2.4.8 Stream positionieren
    gp 2.4.9 Puffer kontrollieren
    gp 2.4.10 Datei löschen und umbenennen
    gp 2.4.11 Temporäre Dateien erstellen
  gp 2.5 Mit Verzeichnissen arbeiten
    gp 2.5.1 Ein neues Verzeichnis anlegen – mkdir()
    gp 2.5.2 In ein Verzeichnis wechseln – chdir(), fchdir()
    gp 2.5.3 Ein leeres Verzeichnis löschen – rmdir()
    gp 2.5.4 Format eines Datei-Eintrags in struct dirent
    gp 2.5.5 Einen Verzeichnisstream öffnen – opendir()
    gp 2.5.6 Lesen aus dem DIR-Stream – opendir() und Schließen des DIR-Streams – closedir()
    gp 2.5.7 Positionieren des DIR-Streams
    gp 2.5.8 Komplettes Verzeichnis einlesen – scandir()
    gp 2.5.9 Ganze Verzeichnisbäume durchlaufen – ftw()
  gp 2.6 Fehlerbehandlung
  gp 2.7 Ausblick


Rheinwerk Computing

2.5 Mit Verzeichnissen arbeitedowntop

In gewisser Weise stellen die Aktionen auf Verzeichnisse auch eine Art Ein-/Ausgabe dar. Sie können ein neues Verzeichnis anlegen oder löschen, in ein Verzeichnis wechseln oder daraus lesen. Da Linux (und UNIX) ja in gewisser Weise nicht zwischen Dateien und Verzeichnisse unterscheidet, gelten hier auch ähnliche Zugriffsrechte, wie Sie diese schon bei den Dateien kennen gelernt haben. Nur hat man mit den Zugriffsrechten für ein Verzeichnis »vier Rechte« (rwxt), eine Datei nur zwei »rw« (das »x« von der Datei kann man sich immer selbst hinzufügen - außer SUID-Dateien -, indem man die Datei ins Homeverzeichnis kopiert. Beim Verzeichnis haben Sie: lesen (r), Datei anlegen/löschen (w), ins Verzeichnis reingehen (x), Datei bedingt löschen (t/!t). Lesen ist nur möglich, wenn man in ein Verzeichnis eintreten kann, weshalb oben »rx« steht.


Rheinwerk Computing

2.5.1 Ein neues Verzeichnis anlegen – mkdir()  downtop

Um ein neues Verzeichnis anzulegen, können Sie die Funktion mkdir() verwenden.

#include <sys/types.h>
#include <sys/stat.h>
int mkdir(const char *pfadname, mode_t modus);

Damit legen Sie ein neues Verzeichnis mit dem Namen pfadnamen und dem Modus mode (abzüglich aktuelle umask) an (».« und »..« sind sowieso Pseudo-Einträge und daher immer präsent; sie müssen also nicht erstellt werden). Sehr wichtig ist es auch, dass Sie beim Modus entsprechende Execute-Bits setzen (müssen), um überhaupt auf das Verzeichnis zugreifen zu können. Hierzu nun ein kleines Listing zur Demonstration:

/* mymkdir.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, char **argv) {
   mode_t mode = S_IRWXU;
   if(argc < 2) {
         printf("USAGE: %s neues_verzeichnis\n",*argv);
         return EXIT_FAILURE;
      }
   // Alle Zugriffsrechte der Einschränkungsmaske erlauben
   umask(0);
   if( (mkdir( argv[1], mode)) != -1 )
      printf("Verzeichnis \"%s\" erfolgreich erstellt\n",
         argv[1]);
   else
      printf("Konnte Verzeichnis nicht erstellen (%s)\n",
         strerror(errno));
   return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

$ gcc -o mymkdir mymkdir.c
$ ./mymkdir
usage: mymkdir neues_verzeichnis
$ ./mymkdir test
Verzeichnis "test" erfolgreich erstellt
$ ls -ld test
drwx------    2 tot      users          48 2003–10–28 23:31 test
$ ./mymkdir test
Konnte Verzeichnis nicht erstellen (File exists)

Sofern versucht wird, ein Verzeichnis zu erstellen, das bereits existiert, wird dies fehlschlagen (wie Sie bei der Ausführung des Programms sehen konnten). errno wird dann auf einen entsprechenden Wert gesetzt. Mehr zu errno erfahren Sie am Ende des Kapitels.


Hinweis   Wollen Sie beim Erstellen eines Verzeichnisses bestimmte Zugriffsrechte vergeben, müssen Sie natürlich auch hier, wie bei den Dateien, die Einschränkungsmaske berücksichtigen bzw. entsprechend setzen (siehe umask()).



Rheinwerk Computing

2.5.2 In ein Verzeichnis wechseln – chdir(), fchdir()  downtop

Wollen Sie während der Programmausführung in ein anderes Verzeichnis wechseln, so lässt sich dies mit den Funktionen chdir() bzw. fchdir() bewerkstelligen.

#include <unistd. h.>
int chdir(const char *pfad);
int fchdir(int fd);

Bei Erfolg geben beide Funktionen 0, ansonsten bei einem Fehler -1 zurück. fchdir() erwartet im Gegensatz zu chdir(), anstatt eines relativen oder absoluten Pfadnamens, einen Filedeskriptor. Mit diesen Funktionen kann allerdings nur immer das aktuelle Arbeitsverzeichnis des Prozesses gewechselt werden. Sobald sich das Programm beendet, wird automatisch wieder in das Elternverzeichnis zurück gewechselt, von wo der Prozess gestartet wurde.


Hinweis   fchdir() ist kein Standard von POSIX und wird nur von SVR4 und 4.4BSD angeboten. Für »wird nur von … angeboten« gilt jedoch, dass es auch in der GNU C-Bibliothek (GLIBC) und somit auch für Linux verfügbar ist ...!


Hierzu, erweitert, das Beispiel des vorherigen Abschnitts. Es soll in das neu erstellte Verzeichnis mit chdir() gewechselt werden.

/* mychdir.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd. h.>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MAX 256
int main(int argc, char **argv) {
   mode_t mode = S_IRWXU;
   char wd[MAX];
   if(argc < 2) {
         printf("USAGE: %s neues_verzeichnis\n",*argv);
         return EXIT_FAILURE;
      }
   // Alle Zugriffsrechte der Einschränkungsmaske erlauben
   umask(0);
   if( (mkdir( argv[1], mode)) != -1 )
      printf("Verzeichnis \"%s\" erfolgreich erstellt\n",
         argv[1]);
   else
      printf("Konnte Verzeichnis nicht erstellen (%s)\n",
         strerror(errno));
   if( (chdir(argv[1]) ) == -1)
      printf("Konnte das Verzeichnis nicht wechseln\n");
   if( NULL != getcwd(wd, MAX) )
      printf("Neues Arbeitsverzeichnis: %s\n",wd);
   return EXIT_SUCCESS;
}

Das Beispiel im Einsatz:

$ gcc -o mychdir mychdir.c
$ ./mychdir test
Verzeichnis "test" erfolgreich erstellt
Neues Arbeitsverzeichnis: /home/tot/test
$ pwd
/home/tot

In diesem Beispiel wurde auch gleich die Funktion getcwd() verwendet. Damit lässt sich ermitteln, in welchem Arbeitsverzeichnis sich der laufende Prozess gerade befindet. Gleiches erreichen Sie in der Kommandozeile mit pwd, wie eben demonstriert. Hier die Syntax zu getcwd():

#include <unistd. h.>
char *getcwd(char *puffer, size_t size);

Damit wird an die Speicheradresse puffer der Pfadname des aktuellen Arbeitsverzeichnisses geschrieben. Die Größe des Puffers wird mit size angegeben. Bei einem Fehler gibt diese Funktion den NULL-Zeiger zurück.


Rheinwerk Computing

2.5.3 Ein leeres Verzeichnis löschen – rmdir()  downtop

Zum Löschen eines bereits leeren Verzeichnisses können Sie die Funktion rmdir() verwenden.

#include <unistd. h.>
int rmdir(const char *pfadname);

Konnte das Verzeichnis pfadname gelöscht werden, gibt diese Funktion 0, ansonsten bei einem Fehler –1 zurück. Wenn Grundkenntnisse unter Linux/UNIX vorhanden sind, wissen Sie, dass das Verzeichnis erst dann gelöscht werden kann, wenn es leer ist. Absolut »leer« heißt, es dürfen nur noch die Einträge . und .. enthalten sein. Löschen Sie ein Verzeichnis mit der Funktion rmdir(), während ein anderer Prozess auf dieses Verzeichnis zugreift und eine Datei anlegen will, so schlägt das Anlegen der Datei fehl (trotzdem aber existiert das Verzeichnis immer noch auf der Festplatte, da ja ein Programm drauf zugreift). Denn wenn der Linkzähler des Verzeichnisses auf 0 steht, sind die beiden Verzeichnislinks . und .. bereits gelöscht. Daher ist es nicht mehr möglich, eine Datei anzulegen. Dennoch kann ein Prozess immer noch Dateien aus dem einen Verzeichnis namens /tmp/abc/* offen haben, während /tmp/abc schon längst mit rm -Rf gelöscht wurde!


Hinweis am Rande zu ».« und »..«  Es sind Pseudo-Einträge, die nicht gelöscht werden können.



Rheinwerk Computing

2.5.4 Format eines Datei-Eintrags in struct dirent  downtop

Nachdem Sie erfahren haben, wie Sie ein Verzeichnis erstellen, in dieses wechseln und dieses wieder löschen können, werden Sie sich wohl gefragt haben, wie so ein Eintrag einer Datei in einem Verzeichnis aussieht. POSIX schreibt diese Struktur vor und sollte in der Headerdatei dirent.h definiert sein. Diese Struktur benötigen Sie, wenn Sie Informationen zu einem Verzeichniseintrag benötigen. Die Struktur könnte folgendermaßen aussehen:

struct dirent {
   ino_t d_ino;
   unsigned char d_type;
   unsigned char d_namlen;
   char d_name[NAME_MAX+1];
};

Wobei die einzige Komponente, die auf wirklich jedem POSIX-System vorhanden ist, die Strukturvariable d_name ist. Diese Komponente ist ein \0-terminierter Dateiname mit einer Größe von NAME_MAX+1 Zeichen. Die Konstante NAME_MAX ist in der Regel mit einem Wert von mindestens 255 definiert. Dies hängt davon ab, welche Länge für Dateinamen auf dem System zulässig ist.

Bei der Strukturvariablen d_ino handelt es sich um die Inode-Nummer (darauf wird noch eingegangen), und d_namlen (wie gesagt, nicht POSIX) beinhaltet die aktuelle Länge des Eintrags in d_name ohne das Stringendezeichen.

Die Strukturvariable d_type beinhaltet die Art der Datei. Dabei sind folgende Konstanten möglich:

gp  DT_UNKNOWN – unbekannt
gp  DT_REG – normale Datei
gp  DT_DIR – ein Verzeichnis
gp  DT_FIFO – eine benannte Pipe oder ein FIFO
gp  DT_SOCK – ein Socket
gp  DT_CHR – zeichenorientierte Gerätedatei
gp  DT_BLK – blockorientierte Gerätedatei

Diese symbolischen Konstanten sind bis jetzt nur unter BSD vorhanden. Dies können Sie am besten selbst mit einer bedingten Kompilierung wie folgt überprüfen:

#include <dirent.h>
#ifdef _DIRENT_HAVE_D_TYPE
   #define HAVE_D_TYPE 1  /* Let's rock */
#else 
   #define HAVE_D_TYPE 0  /* ansonsten bleibt nur struct stat */
#endif

Ist die symbolische Konstante _DIRENT_HAVE_D_TYPE gesetzt, können Sie die Strukturvariable d_type verwenden. Ansonsten müssen Sie die Art der Datei wie üblich über st_mode aus der struct stat ermitteln (siehe Kapitel 3). Kann die Art der Datei nicht mit d_type ermittelt werden, wird die Strukturvariable auf DT_UNKNOWN gesetzt. In absehbarer Zukunft dürften aber alle Systeme auf d_type zugreifen können (Ausnahme: Old-Timer-Betriebssysteme, die nicht mehr mit Updates erneuert werden können).


Hinweis   Müssen Sie die Strukturvariable d_type in einen st_mode-Wert der Struktur stat konvertieren oder umgekehrt, dann sollten Sie sich die Makros IFTODT() und DTTOIF() ansehen, die ebenfalls in der Headerdatei <dirent.h> definiert sind.


Benötigen Sie außerdem weitere Attribute, wie z. B. die Größe oder die letzte Veränderung einer Datei, so möchte ich Sie hierzu auf das Kapitel der Dateiattribute verweisen (Kapitel 3).


Rheinwerk Computing

2.5.5 Einen Verzeichnisstream öffnen – opendir()  downtop

Um einen Stream zu einem Verzeichnis zu erhalten, schreibt POSIX vor, den DIR-Datentyp zu verwenden, der eine interne Struktur darstellt, auf den die noch folgenden Funktionen ebenfalls zugreifen. Wer hier Ähnlichkeiten mit dem FILE-Stream oder einem Filedeskriptor sieht, liegt gar nicht so falsch. Früher wurden Verzeichnisse auch auf diese Art gelesen. Man verwendete z. B. open() oder fopen() zum Öffnen des Arbeitsverzeichnisses (.) und hatte anschließend die Struktur dirent mit read() oder fread() ausgelesen. Dies hat dann in etwa wie folgt ausgesehen:

#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
    FILE *fp;
    struct dirent *dir;
    int n;
    /* Veraltete Methode–sollte nicht mehr funktionieren! */
    if ((fp = fopen(".", "r")) == NULL) {
           perror("current directory");
           exit(EXIT_FAILURE);
        }
    while((n=fread(dir, sizeof(struct dirent), 1, fp)) >0) {
           if (dir->d_ino == 0)
               continue;
           printf("%.*s\n", NAME_MAX+1, dir->d_name);
        }
    fclose(fp);
    return EXIT_SUCCESS;
}

Da allerdings mit der Zeit immer mehr neue Systeme und somit auch immer unterschiedlichere und systemabhängigere Verzeichnisformate entwickelt wurden, musste man sich eine einheitlichere Lösung suchen. Kurzum, es sprang POSIX ein und schrieb die Funktionen opendir(), readdir(), rewinddir() und closedir() vor. Neu hinzugekommen sind auch die Funktionen telldir() und seekdir(). Wobei die letzten beiden Funktionen »nur« BSD-konform sind. Sicherlich hätte man sich diesbezüglich einen ANSI C-Standard gewünscht, aber dies ist, wegen der vielen unterschiedlichen Verzeichnisformate auf den Systemen, ein recht hoffnungsloses Unterfangen.


Hinweis   Hoffnungslos heißt natürlich nicht aussichtslos. Entweder schreibt man für jedes System den entsprechenden Code oder nutzt eine plattformübergreifende Bibliothek, z. B. die HXdir-Implementation von libHX, die einen opendir()-ähnlichen Layer nebst Linux auch für DOS und Win 32 bereitstellt. Auf der Webseite des Fachgutachters (http://linux01.org:2222/prog-libHX.php) dieses Buches findet man einen solchen opendir()-Style-Layer.


Hier die Syntax zur Funktion opendir():

#include <dirent.h>
#include  <sys/types.h>
DIR *opendir(const char *dirname);

Die Funktion gibt bei Erfolg den DIR-Zeiger auf das geöffnete Verzeichnis dirname zurück. Bei einem Fehler wird der NULL-Zeiger zurückgegeben. Welcher Fehler aufgetreten ist, können Sie mit der Fehlervariablen errno auswerten.

Der Typ DIR ist intern als Filedeskriptor implementiert, und die opendir()-Funktion stellt im Prinzip nichts anderes als die elementare Funktion open() dar. Sicherlich ist Ihnen ein paar Seiten zuvor die Funktion fchdir() aufgefallen, die einen Filedeskriptor als Argument erwartet. Um aus einem DIR-Zeiger einen Filedeskriptor zu machen, bietet Ihnen die GNU C Library die Funktion dirfd() an.

int dirfd(DIR *dirstream);

Diese Funktion gibt den Filedeskriptor des DIR-Streams zurück, den Sie zuvor mit opendir() geöffnet haben:

DIR *dp;
int fd;
dp = opendir( dirname );
if( dp != NULL ) {
      fd = dirfd( dp );
      if( fd == -1 )
         exit(EXIT_FAILURE);
   }
if( (fchdir(fd) ) == -1)
     printf("Konnte das Verzeichnis nicht wechseln\n");

Bei einem Fehler gibt die Funktion dirfd() den Wert -1 zurück. dirfd() ist nicht Bestandteil von POSIX, sondern lediglich von BSD und SVR4.


Rheinwerk Computing

2.5.6 Lesen aus dem DIR-Stream – opendir() und Schließen des DIR-Streams – closedir(downtop

Wenn Sie jetzt den DIR-Stream zuvor mit opendir() geöffnet haben, können Sie aus diesem mit der Funktion readdir() lesen. Hier die Syntax dazu:

#include <dirent.h>
#include  <sys/types.h>
struct dirent *readdir(DIR *dirstream);

Damit wird immer der nächste Eintrag der Verzeichnisdatei vom DIR-Stream gelesen. Bei Erfolg wird ein Zeiger auf die Struktur dirent zurückgegeben oder bei einem Fehler NULL. Gewöhnlich wird die Funktion readdir() in einer Schleife eingesetzt, die so lange durchlaufen wird, bis nichts mehr zum Lesen vorhanden ist, also NULL.


Hinweis   Beachten Sie bitte, dass nicht auf jedem System das Arbeitsverzeichnis (.) und das Elternverzeichnis (..) mit ausgegeben werden. Linux fällt nicht darunter, denn was zu viel ist, kann man noch im Programm wegfiltern, was zu wenig ist, kann man sich nicht holen.


Hierzu ein einfaches Listing, womit das aktuelle Arbeitsverzeichnis ausgelesen und ausgegeben wird. Des Weiteren wird versucht, sofern die symbolische Konstante _DIRENT_HAVE_D_TYPE gesetzt ist, zu ermitteln, ob es sich dabei um eine normale Datei oder um ein Verzeichnis handelt. Sofern bei Ihrem System _DIRENT_HAVE_D_TYPE gesetzt ist, aber (noch) nicht unterstützt wird, ist die Strukturvariable d_type auf DT_UNKNOWN gesetzt. Zum Ermitteln von Dateitypen und weiteren Attributen folgt extra ein Kapitel (Kapitel 3).


Hinweis   readdir() gibt nicht alphabetisch zurück, auch wenn das bei einer Ausgabe mit folgendem Listing so erscheinen mag. Zurückgegeben wird es so, wie das Dateisystem es abgelegt hat. Wer natürlich Dateien in alphabetischer Reihenfolge anlegt, kann sie durchaus auch im alphabetischen Zustand wieder holen. »ls« ist Ausnahme, da das Programm immer sortiert (Ausnahme: Option -f).


/* list_wd.c */
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
int main (void) {
  DIR *dp;
  struct dirent *ep;
  static int files = 0;
  dp = opendir (".");
  if (dp != NULL) {
      while ((ep = readdir (dp)) != NULL) {
#ifdef _DIRENT_HAVE_D_TYPE
           switch(ep->d_type) {
              case DT_REG     : printf("Normale Datei : ");
                                break;
              case DT_DIR     : printf("Verzeichnis   : ");
                                break;
              /* ... */
              case DT_UNKNOWN : printf("???       : ");
                                break;
              }  
#endif
           printf("%s\n", ep->d_name);
           files++;
        }
      closedir (dp);
    }
  else
      perror ("Kann Verzeichnis nicht öffnen");
  printf("--- Insg. %d Dateien im Verzeichnis ---\n",files);
  return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

$ gcc -o list_wd list_wd.c
$ ./list_wd
???       : .
???       : ..
???       : .qt
???       : .ddd
???       : .kde
???       : .xim
???       : dir1
???       : dir2
???       : .netscape
???       : test
???       : Documents
...
...
??       : projects
???       : .xtalkrc
???       : out.txt
???       : Verzeichnisse.doc
???       : evolution
???       : .xsession-errors
--- Insg. 116 Dateien im Verzeichnis ---

In diesem Fall ist die symbolische Konstante _DIRENT_HAVE_D_TYPE zwar gesetzt, aber immer auf DT_UNKNOWN.


Hinweis   Dateien, die mit einem Punkt beginnen, sind unter Linux/UNIX in der Regel nicht sichtbar (also mit »ls«), werden aber mit readdir() natürlich gelesen. Umsteiger von Windows können dies mit dem Attribut »Hidden -> Versteckt« vergleichen.

Hinweis   Die Funktion readdir() eignet sich nicht zur Programmierung von Threads, da mehrere Threads, die denselben Verzeichnisstream verwenden, den Rückgabewert überschreiben. Bei den Threads werden die Funktion readdir_r() oder eben das libHX-Pendant empfohlen.


Um einen DIR-Stream ordnungsmäßig nach der Verwendung wieder freizugeben, wird die Funktion closedir() eingesetzt.

#include <dirent.h>
#include  <sys/types.h>
int closedir( DIR *dirstream );

Konnte der Stream erfolgreich geschlossen werden, wird 0, ansonsten bei einem Fehler -1 zurückgegeben.


Rheinwerk Computing

2.5.7 Positionieren des DIR-Streams  downtop

Ebenfalls wie schon bei den elementaren und Standard-E/A-Funktionen gibt es auch für den DIR-Zeiger eine Möglichkeit, die Position zu verändern.

Wollen Sie den DIR-Stream wieder an den Anfang setzen, so dass readdir() wieder beim ersten Verzeichniseintrag anfängt zu lesen, können Sie die Funktion rewinddir() benutzen.

#include <sys/types.h>
#include <dirent.h>
void rewinddir( DIR *dirstream );

Die aktuelle Position des DIR-Streams können Sie mit der Funktion telldir() ermitteln.

#include <sys/types.h>
#include <dirent.h>
off_t telldir (DIR *dirstream) ;

Es wird empfohlen, den Rückgabewert später wieder mit der Funktion seekdir() zum Zurücksetzen dieser Position zu verwenden. Die Syntax zu seekdir():

#include <sys/types.h>
#include <dirent.h>
void seekdir (DIR *dirstream, off_t pos);

Damit setzen Sie den DIR-Zeiger auf die Position von pos. pos muss ein Wert sein, den Sie zuvor mit der Funktion telldir() ermittelt haben oder der im Gültigkeitsbereich liegt. Welche Werte jedoch gültig sind, verrät uns auch nicht die Man-Page.

Beide Funktionen, telldir() und seekdir(), sind nicht POSIX-konform und nur in BSD4.3 vorhanden.


Rheinwerk Computing

2.5.8 Komplettes Verzeichnis einlesen – scandir()  downtop

Eine Funktion auf einer etwas höheren Ebene ist scandir(). Wobei man gleich anmerken muss, dass diese Funktion auch nicht dem POSIX-Standard entspricht. Aber da diese doch auf jedem Linux-System vorhanden ist, sollte sie hier erwähnt werden. Hierzu die Syntax:

#include <dirent.h>
int scandir( const char *dirname, struct dirent **namelist, 
             int (*selector) (const struct dirent *), 
             int (*cmp) (const void *, const void *)   ) ;

Mit dieser Funktion lesen Sie die Einträge des Verzeichnisses dirname ein. Als Ergebnis finden Sie alle Einträge eines Verzeichnisses in *namelist, einem Array von Zeigern des Typs struct dirent. Die Funktion nimmt die dynamische Speicherreservierung für *namelist automatisch vor. Freigeben müssen Sie den Speicherplatz allerdings wieder selbst mittels free(). Bei einem Programm, das sich nach dem Einlesen der Verzeichniseinträge gleich wieder beendet, ist es nicht so schlimm, wenn Sie den Speicher nicht mehr freigeben, da dieser bei Beendigung automatisch wieder freigegeben wird (verfallen Sie aber bloß nicht in diese Gewohnheit bei Programmen, die länger als »trivial« sind!). Bei dauerhaft laufenden Programmen schaffen Sie ein klassisches Speicherleck (engl. Memory Leak), wenn Sie den zugeteilten Speicher nicht wieder freigeben.

Die Einträge in *namelist werden außerdem von der Funktion cmp sortiert. Da diese Funktion zwei Argumente vom Typ struct dirent ** vergleichen muss, sind die Funktionen strcmp() oder strcoll() nicht direkt dafür geeignet. Sie sollten stattdessen die implementierten Funktionen alphasort() oder versionsort() verwenden. Mehr zu den Sortierfunktionen finden Sie in den entsprechenden Manual-Pages. Im anschließenden Beispiel werden Sie alphasort() verwenden, womit die einzelnen Einträge alphabetisch sortiert werden.

Wollen Sie nur bestimmte Verzeichniseinträge bearbeiten oder ausgeben lassen, können Sie mit der Funktion selector eine eigene Funktion verwenden und selbst entscheiden, welche Einträge ausgewählt werden. Es werden dabei nur die Einträge ausgewählt, bei denen nicht 0 zurückgegeben wird. Diese Einträge befinden sich dann ebenfalls in *namelist. Natürlich müssen Sie sich auch hierbei keine Gedanken bezüglich der Speicherreservierung machen, aber wiederum schon um das Freigeben des Speichers!

Der Rückgabewert der Funktion scandir() ist die Anzahl der erfolgreichen Einträge, die in *namelist platziert wurden. Bei einem Fehler wird -1 zurückgegeben. Da hier mehrere Fehler auftreten können (wie z. B. kein Zugriff auf das Verzeichnis oder keine Speicherreservierung mit malloc ... möglich), sollten Sie die globale Fehlervariable errno abfragen.

Genug der großen Worte. Lassen Sie uns zur Tat schreiten. Das folgende Beispiel demonstriert, wie Sie mit der Funktion scandir() ein komplettes Verzeichnis mit nur einem Funktionsaufruf einlesen können. Nach dem Funktionsaufruf befinden sich alle Verzeichniseinträge in namelist. Wenn Sie das Programm ohne Argumente aufrufen, wird das aktuelle Arbeitsverzeichnis eingelesen. Ansonsten können Sie als zweites Argument das Verzeichnis angeben, das Sie stattdessen einlesen wollen. Anschließend werden die eingelesenen Einträge ausgegeben und der Speicher ordentlich wieder freigegeben. Hier das Beispiel:

/* scan_dir.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd. h.>
#define MAX 255
int main (int argc, char **argv) {
   int num_entries, i;
   struct dirent **namelist, **list;
   const char *ptr = NULL;
   char wd[MAX];
   if (argc < 2)
      ptr = ".";
   else
      ptr = argv[1];
   if ( (  num_entries = 
           scandir( ptr, &namelist, 0, alphasort)) < 0) {
      fprintf (stderr, "Unerwarteter Fehler\n");
      exit (EXIT_FAILURE);
    }
   chdir (ptr);
   getcwd (wd, MAX);
   printf ("Anzahl der Einträge in %s: %d\n",
      wd, num_entries);
   if (num_entries) {
      printf ("Die Einträge:\n");
      for(i=0, list=namelist; i<num_entries; i++, *list++) {
         printf (" %s\n", (*list)->d_name);
         free (*list);
      }
      free (namelist);
      printf ("\n");
   }
   printf ("\n");
   return EXIT_SUCCESS;
}

Oder mit einem WP (Walking Pointer):

    ...
    if(num_entries > 0) {
        struct dirent **travp = list;
        printf("Die Einträge:\n");
        while(num_entries--) {
            printf("\t" "%s\n", (**travp).d_name);
            free(*travp);
            ++travp;
        }
        free(namelist);
    }
...

Das Programm im Einsatz:

$ gcc -o scan_dir scan_dir.c
$ ./scan_dir /usr
Anzahl der Einträge in /usr: 14
Die Einträge:
 .
 ..
 X11
 X11R6
 bin
 games
 i486-suse-linux
 include
 lib
 local
 sbin
 share
 src
 tmp

Und jetzt noch die zweite Möglichkeit! Sie wählen selbst aus, welche Verzeichniseinträge ausgegeben werden sollen. Hierzu habe ich mir erlaubt, die von POSIX zur Verfügung gestellten Funktionen der Headerdatei <regex.h> zu verwenden. Diese Funktionen können zum Suchen von regulären Ausdrücken verwendet werden.


Hinweis   Mehr zum Thema reguläre Ausdrücke oder gar, wie man diese selbst programmiert, finden Sie in meinem Buch »C von A bis Z«, das sich auch auf der Buch-CD befindet.


In der Headerdatei <regex.h> werden Ihnen folgende vier Funktionen zur Verfügung gestellt: regcomp(), regexec(), regerror() und regfree(). Mit der Funktion regcomp() müssen Sie die Zeichenkette (den regulären Ausdruck) erst in die Variable regex_t übersetzen. Anschließend können Sie diesen regulären Ausdruck mit der Funktion regexec() und dem zu durchsuchenden Text (hier Verzeichniseinträge) anwenden. Am Ende wird die nicht mehr benötigte Variable regex_t mit regfree() wieder freigegeben. Mit regerror() können Sie eventuelle Fehlermeldungen zu den Funktionen regcomp() und regexec() ausgeben lassen. Ich hoffe, Sie verzeihen mir, dass ich dieses Kapitel ein wenig schneller abgehandelt habe, aber die regulären Ausdrücke sind hier nicht das Thema.

Mit dem nun folgenden Beispiel können Sie als drittes Argument einen regulären Ausdruck angeben, der auf das Verzeichnis, das Sie als zweites Argument angeben, verwendet wird. Stimmt der reguläre Ausdruck mit einem Eintrag im Verzeichnis überein, wird dieser mit der eigenen Selector-Funktion wieder (automatisch) dynamisch in *namelist geschrieben. Wichtig ist dabei, dass nur die Einträge in *namelist geschrieben werden, wo die Funktion (im Beispiel: my_select) einen Wert größer als 0 zurückliefert. Hierzu das Listing:

/* find_dir.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <regex.h>
#include <unistd. h.>
#define MAX 255
static int search = 0;
static regex_t regexpr;
static int my_select (const struct dirent *dp) {
   if(search == 0)
      return 1;
   if (regexec(&regexpr, dp->d_name, 0, NULL, 0) == 0)
      return 1;
   else
      return 0;
}
int main (int argc, char **argv) {
   int num_entries, i;
   struct dirent **namelist, **list;
   const char *ptr = NULL;
   char wd[MAX];
   if (argc < 2)
      ptr = ".";
   else
      ptr = argv[1];
   if(argc == 3) {
     if (regcomp(&regexpr, argv[2], REG_EXTENDED)) {
        printf("Problem beim Ausdruck %sn", argv[2]);
        return EXIT_FAILURE;
     }
     search = 1;
    }
   if ( ( num_entries =
          scandir(ptr, &namelist, my_select, 0)) < 0) {
      fprintf (stderr, "Unerwarteter Fehler\n");
      exit (EXIT_FAILURE);
   }
   chdir (ptr);
   getcwd (wd, MAX);
   printf ("Anzahl der Einträge in %s: %d\n",
      wd, num_entries);
   if (num_entries) {
      printf ("Die Einträge:\n");
      for(i=0, list=namelist; i<num_entries; i++, *list++){
         printf (" %s\n", (*list)->d_name);
         free (*list);
      }
      free (namelist);
      printf ("\n");
   }
   printf ("\n");
   regfree(&regexpr);
   return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

$ gcc -o find_dir find_dir.c
$ ./find_dir /usr/include ^std
Anzahl der Einträge in /usr/include: 4
Die Einträge:
 stdio.h
 stdio_ext.h
 stdint.h
 stdlib.h

Hier wurden zum Beispiel alle Einträge im Verzeichnis /usr/include ausgegeben, die mit der Zeichenkette std beginnen. Das Zeichen ^ bedeutet dabei so viel wie »beginne am Anfang«.


Rheinwerk Computing

2.5.9 Ganze Verzeichnisbäume durchlaufen – ftw()  toptop

Manches Mal reicht es nicht aus, in nur einem Verzeichnis etwas auszulesen bzw. etwas zu suchen. Wollen Sie tiefer in die Verzeichnisse vordringen, können Sie entweder eine rekursive Funktion mit den bereits bekannten Funktionen opendir() und readdir() schreiben oder aber auf die XPG4-konforme Funktion, die auf vielen Linux-Systemen vorhanden ist, nämlich ftw() (file tree walk), zurückgreifen. Zwar ruft sich die Funktion ftw() intern ebenfalls mehrmals rekursiv auf, aber es handelt sich immerhin um eine erprobte Bibliotheksfunktion.

Sofern Sie natürlich möglichst portabel bleiben müssen, bleibt Ihnen nichts anderes übrig, als opendir() und readdir() zu verwenden, da der POSIX-Standard eben weiter verbreitet ist als der XPG4-Standard.


Hinweis   Wer auf einem *BSD-System arbeitet, findet hier mit fts() (traverse a file hierarchy) eine – man mag fast schon sagen – interessantere Routine als ftw(). Es wird gar erwartet, dass diese Routinen zukünftig in POSIX.1 aufgenommen werden. Mehr dazu finden *BSD-Anhänger in der entsprechenden Manual Page von fts().


Wie dem auch sei, im Gegensatz zu vielen anderen Büchern will ich hier nicht den üblichen Weg vorstellen, sondern die Funktion ftw().

Hier die Syntax von ftw():

#include <ftw.h>
int ftw( const char *dir, 
         int (*fn)(const char *file, const  struct stat *sb, 
         int flag), int nopenfd   );

Mit dieser Funktion beginnen Sie mit dem Durchforsten eines kompletten Verzeichnisbaums, angefangen bei dir. Für jeden gefundenen Eintrag im Verzeichnis dir (und allen Unterverzeichnissen) wird die Funktion fn() mit dem kompletten Pfadnamen, einem Zeiger auf die Struktur stat (siehe Kapitel 3) und einem Flag, das folgenden Wert haben kann:

gp  FTW_F – Eintrag ist eine normale Datei.
gp  FTW_D – Eintrag ist ein Verzeichnis.
gp  FTW_DNR – Eintrag ist ein nicht lesbares Verzeichnis (meist mangelnde Zugriffsrechte).
gp  FTW_SL – Eintrag ist ein symbolischer Link.
gp  FTW_NS – Fehler bei stat, obwohl der Eintrag kein symbolischer Link ist.

Damit die Funktion ftw() nicht alle für einen Prozess vorhandenen Filedeskriptoren für die offenen Verzeichnisse verwendet, können Sie mit dem Argument nopenfd die maximale Anzahl gleichzeitig geöffneter Verzeichnisse angeben. Sobald die Suchtiefe dann höher ist als nopenfd, bremst ftw(), da es einige Verzeichnisse schließt, um neue zu ändern. Diese müssen hinterher wieder geöffnet werden, um an der Stelle, an der ftw() aufgehört an, weiterzumachen.

Die Funktion ftw() können Sie stoppen, indem Sie bei der Funktion fn() einen Wert ungleich 0 zurückgeben. 0 heißt weitermachen und ungleich 0 stopp. Ansonsten macht ftw() so lange weiter, bis alle Verzeichnisse und deren Einträge durchforstet sind. Tritt ein Fehler auf, wird -1 zurückgegeben.

In dem folgenden Listing soll ein komplettes Verzeichnis mitsamt seinen Unterverzeichnissen ausgegeben werden. Es werden dabei alle Einträge angezeigt. Das Programm wurde mit Absicht relativ einfach und kurz gehalten. Tritt z. B. das Flag FTW_F in Erscheinung, sollten Sie die Strukturvariable st_mode von stat abfragen, ob es sich denn um eine normale Datei (S_IFREG), eine zeichenorientierte Gerätedatei (S_IFCHR), eine blockorientierte Gerätedatei (S_IFBLK), ein FIFO (S_IFIFO), einen symbolischen Link (S_IFLNK) oder ein Socket (S_IFSOCK) handelt. Natürlich können Sie auch weitere Attribute der einzelnen Einträge von der Struktur stat abfragen. Mehr zu stat im nächsten Kapitel. Hier das komplette Listing:

/* ftwalk.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <ftw.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
static int dir_deep(const char *);
static int dir_deep(const char *pfad) {
   int deep = 0;
   char *ptr = (char *)pfad;
   while(ptr=strchr(ptr, '/')) {
      ptr++;
      deep++;
   }
   return deep;
} 
static int
fn(const char *pfadname, struct stat *attribut, int flag) {
   static int first = 1;
   static int deep;
   int i;
   const char *ptr = pfadname;
  
   if(!first) {
      for( i = 1; i<=dir_deep(pfadname) - deep; i++)
         printf("%2c|"' ');
      printf("---[%s",strrchr(pfadname, '/')+1);
   }
   else {
      deep=dir_deep(pfadname);   
      printf("[%s",pfadname);
      first = 0;
   }
   switch(flag) {
      case FTW_F   : printf("]\n");    break;
      case FTW_D   : printf("/]\n");   break;
      case FTW_DNR : printf("/-]\n");  break;
      default      : printf("???]\n"); break;
   }
   return 0;
   /* Wollen Sie ftw() stoppen, geben Sie 1 zurück */
   //return 1;
}
int main(int argc, char **argv) {
   const char *ptr = NULL;
   if(argc < 2)
      ptr = ".";
   else
      ptr = argv[1];
   if( ftw( ptr, fn, 16) == 0 )
      return EXIT_SUCCESS;
   else
      printf("Fehler bei ftw() ...!\n");
   return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

$ gcc -o ftwalk ftwalk.c
$ ./ftwalk /home | less
[/home/]
  |  |---[tot/]
  |  |  |---[.qt/]
  |  |  |  |---[qtrc]
  |  |  |  |---[qt_plugins_3.1rc]
  |  |  |  |---[.qt_plugins_3.1rc.lock]
  |  |  |  |---[.qtrc.lock]
  |  |  |---[.ddd/]
  |  |  |  |---[log]
  |  |  |  |---[init]
  |  |  |  |---[tips]
  |  |  |  |---[themes/]
  |  |  |  |---[history]
  |  |  |  |---[sessions/]
  |  |  |---[.kde/]
...
...
  |  |  |  |---[private/]
  |  |  |  |  |---[config.xmldb]
  |  |  |---[.xsession-errors]
  |  |  |---[.xsession]
  |  |  |---[.xserverrc.secure]
  |  |  |---[.dvipsrc]
  |  |  |---[.mailcap]
  |  |  |---[.fonts.cache-1]

Da die Ausgabe kompletter Verzeichnisse recht umfangreich werden kann, wurde hier eine Pipe durch less verwendet. Damit wird die Ausgabe des Programms ftwalk auf less umgeleitet, und Sie können bequem die Ausgabe nach unten und oben scrollen. Mit »q« können Sie less vorzeitig wieder beenden.

 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern