2.5 Mit Verzeichnissen arbeiten
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.
2.5.1 Ein neues Verzeichnis anlegen – mkdir()
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()).
|
2.5.2 In ein Verzeichnis wechseln – chdir(), fchdir()
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.
2.5.3 Ein leeres Verzeichnis löschen – rmdir()
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.
|
2.5.4 Format eines Datei-Eintrags in struct dirent
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:
|
DT_UNKNOWN – unbekannt |
|
DT_REG – normale Datei |
|
DT_DIR – ein Verzeichnis |
|
DT_FIFO – eine benannte Pipe oder ein FIFO |
|
DT_SOCK – ein Socket |
|
DT_CHR – zeichenorientierte Gerätedatei |
|
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).
2.5.5 Einen Verzeichnisstream öffnen – opendir()
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.
2.5.6 Lesen aus dem DIR-Stream – opendir()
und Schließen des DIR-Streams – closedir()
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.
2.5.7 Positionieren des DIR-Streams
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.
2.5.8 Komplettes Verzeichnis einlesen – scandir()
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(®expr, 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(®expr, 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(®expr);
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«.
2.5.9 Ganze Verzeichnisbäume durchlaufen – ftw()
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:
|
FTW_F – Eintrag ist eine normale Datei. |
|
FTW_D – Eintrag ist ein Verzeichnis. |
|
FTW_DNR – Eintrag ist ein nicht lesbares Verzeichnis (meist mangelnde Zugriffsrechte). |
|
FTW_SL – Eintrag ist ein symbolischer Link. |
|
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.
|