Bewegen in Verzeichnissen unter Unix
-
Thema: Bewegen in Verzeichnissen unter Linux/Unix
Was benoetigt man?Nun grundsaetzlich kann ich nicht sagen, was du benötigst,
da es immer davon abhängt, was genau man programmieren will. Aber
ich denke, dass ich die wichtigsten Dinge zusammentragen kann.Ich werde ein paar Funktionen vorstellen und das ganze anhand eines
Beispieles (Ein Directory kopieren) mehr erklären. Sollte ich Fehler
machen, bitte ich natürlich um Korrektur!Okay, also was benätige ich?
Zu erst einmal muss ich feststellen können, ob es sich bei einer
Dateiart um ein Directory, eine Normale Datei (regular file) oder
eine spezielle Art einer Datei (special file) handelt.
Zu diesem Zweck gibt es drei Funktionen:int stat ( const char *PathName, struct stat *Buffer ); [man: stat(2)]
*int fstat ( int FD, struct stat Buffer ); [man: fstat(2)]
int lstat ( const char *PathName, struct stat *Buffer ); [man: lstat(2)]Diese Funktionen sind in der Headerdatei <sys/stat.h>
definiert. Ebenfalls wird die Headerdatei <sys/types.h>
benoetigt.Wer ein Unixsystem nutzt, sollte darauf Acht geben, wie
die Headerdateien includiert werden, da bei manchen Unixsystemen
keine Rekursive Includierung der Headerdateien untereinander vor-
handen ist. Im o. Fall saehe eine Includierung so aus:#include <sys/types.h> #include <sys/stat.h>
Kurze Erlaeuterung der Funktionen:
int stat ( const char *PathName, struct stat *Buffer );
Schreibt die Attribute der Datei, die in 'PathName' spezifiziert ist,
in die Strukturvariable 'Buffer'. Handelt es sich bei der unter
'PathName' spezifizierten Datei um einen symbolischen Link, so werden
die Attribute der Datei, auf die der symbolische Link zeigt, in die
Strukturvariable 'Buffer' geschrieben.
(siehe man: stat(2))*int fstat ( int FD, struct stat Buffer );
Schreibt die Attribute der schon geoeffneten Datei mit dem Datei-
descriptor 'FD' in die Strukturvariable 'Buffer'
(siehe man: fstat(2))int lstat ( const char *PathName, struct stat *Buffer );
Verhaelt sich wie die stat-Funktion, mit dem Unterschied, dass, wenn
es sich bei der unter 'PathName' spezifizierten Datei um einen
symbolischen Link, so werden die Attribute dieses symbolischen Links
selbst in die Strukturvariable 'Buffer' geschrieben.
(siehe man: lstat(2))Aufbau der Struktur 'stat'
Die Struktur kann sich in den einzelnen Unix-Distributionen etwas
unterscheiden, sie sollte allerdings so aussehen:Alle drei Funktionen geben zurueck: 0 bei Erfolg; -1 bei Fehler
struct stat { mode_t st_mode; //Dateiart und Zugriffsrechte ino_t st_ino; //i-node Nummer dev_t st_dev; //Geraetenummer (Dateisystem) dev_t st_rdev; //Geraetenummer fuer Geraetedatei nlink_t st_nlink; //Anzahl der Links auf die Datei uid_t st_uid; //User-ID des Eigentuemers gid_t st_gid; //Group-ID des Eigentuemers off_t st_size; //Groeße in Byte fuer normale Dateien time_t st_atime; //Zeit d. letzten Zugriffs (access time) time_t st_mtime; /*Zeit d. letzten Aenderung in der Datei (modification time)*/ time_t st_ctime; //Zeit der letzten Aenderung der i-node long st_blksize; //voreingestellte Blockgroeße long st_blocks; //Anzahl der benoetigten 512-Byte-Bloecke };
Alle Elemente, bis auf st_rdev, st_blksize und
st_blocks, sind von POSIX.1 vorgeschrieben und sind auf allen
Systemen vorhanden.Ihr koennt die Funktionsweise von der stat-Funktion ja einmal aus-
probieren, indem ihr ein kleines Programm schreibt. Das koennte dann
ungefaehr so aussehen:#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> int main() { char PathName[30]; struct stat FileInfo; printf("Pfad + Dateiname: "); scanf("%s",PathName); if (stat(PathName,&FileInfo) == -1) { perror("stat()"); return EXIT_FAILURE; } if (S_ISREG(FileInfo.st_mode)) puts("Normale Datei"); else puts("Spezielle Datei"); return EXIT_SUCCESS; }
In diesem Code taucht ein Makro auf: S_ISREG()
Es gibt eine Reihe von Makros, die Auskunft darueber geben, um was
es sich bei einer Datei handelt. Ich werde hier alle vom POSIX.1
vorgeschriebenen Makros kurz beschreiben. Die Makros werden immer
auf das Element st_mode einer Stat-Strukturvariablen
angewandt.S_ISREG()
Liefert einen Wert ungleich 0, wenn es sich um eine regulaere Datei
handeltS_ISDIR()
Liefert einen Wert ungleich 0, wenn es sich um ein Directory handeltS_ISCHR()
Liefert einen Wert ungleich 0, wenn es sich um ein zeichenorientiertes
Geraet handeltS_ISBLK()
Liefert einen Wert ungleich 0, wenn es sich um ein blockorientiertes
Geraet handeltS_ISFIFO()
Liefert einen Wert ungleich 0, wenn es sich um eine Pipe oder FIFO
handeltIhr koennt ja mit diesen Makros ein wenig experimentieren.
Kommen wir nun zu den Funktionen, die wir benoetigen um uns unter
Linux/Unix in Diretorys fortzubewegen.DIR *opendir ( const char *Path ); [man: opendir(3)]
-> Rueckgabe: DIR-Zeiger bei Erfolg; NULL bei Fehler
struct dirent *readdir ( DIR *Ptr ); [man: readdir(3)]
-> Rueckgabe: dirent-Zeiger bei Erfolg; NULL bei Fehler
*void rewinddir ( DIR Ptr ); [man: rewinddir(3)]
*int closedir ( DIR Ptr ); [man: closedir(3)]
-> Rueckgabe: 0 bei Erfolg; -1 bei FehlerWie man sieht, benoetigt man noch mehr. Naemlich die Struktur
DIR und dirent.Die Struktur DIR ist eine Systeminterne Struktur, die Infor-
mationen ueber das zu lesende Directory speichert.dirent ist eine Struktur, die laut POSIX.1 mindestens den
Dateinamen enthalten muss. Sie kann auch noch die i-node-Nummer
enthalten.Diese Struktur ist wie folgt definiert:
struct dirent { char d_name[NAME_MAX + 1]; //Dateiname mit '\0' ino_t d_ino; //i-node-Nummer };
Die Strukturen und die o. g. Funktionen sind in der Headerdatei
<dirent.h> definiert.Kurze Erlaeuterung der Funktionen:
DIR *opendir ( const char *Path );
Oeffnen des Directory 'Path'
(siehe man: opendir(3))struct dirent *readdir ( DIR *Ptr );
Mit 'readdir' ließt man schrittweiße die Eintraete in einem Directory
Der erste Aufruf liefert die erste Datei in einem Directory, jeder
weitere Aufruf die naechste Datei. Dabei muessten die Dateien nicht
in der Reihenfolge gelesen werden, wie es ein 'ls -l'-Befehl anzeigt
(siehe man: readdir(3))*void rewinddir ( DIR Ptr );
Setzt den Lesezeiger zurueck auf den Anfang der Namesliste des
Directorys
(siehe man: rewinddir(3))*int closedir ( DIR Ptr );
Schließt ein Directory wieder (wer haetts gedacht )
(siehe man: closedir(3))Das ist eigentlich schon alles. Ich werde die Rechte eines Directorys
jetzt mal nicht beachten. Dafuer muesste ich fuer die Rechte etwas
mehr ausholen. Das kann ich ja dann machen, wenn mir wiedermal
langweilig ist .Machen wir mal ein kleines Beispiel. Einfach ein Programm, welches
den Inhalt eines Directorys ausließt und ausgibt, ob es sich um
eine regulaere Datei handelt oder nicht.#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> int main () { struct stat FileInfo; struct dirent *CurrentFile; DIR *Directory; char Path[30]; if ( (Directory = opendir("/tmp")) == NULL) { perror("opendir()"); return EXIT_FAILURE; } while ( (CurrentFile = readdir(Directory)) != NULL) { if (strcmp(CurrentFile->d_name,".") && strcmp(CurrentFile->d_name,"..")) { strcpy(Path,"/tmp/"); strcat(Path,CurrentFile->d_name); printf ("%s \t:",Path); if (stat(Path,&FileInfo) == -1) { perror("stat()"); closedir(Directory); return EXIT_FAILURE; } if (S_ISREG(FileInfo.st_mode)) puts("Regulaere Datei"); else if (S_ISDIR(FileInfo.st_mode)) puts("Directory"); else puts("Spezielle Datei"); } } //end while closedir(Directory); return EXIT_SUCCESS; } //end main
Erklärung zum Code:
Zunaechst einmal legen wir ein paar Variablen an. Die ersten drei
dürften jetzt klar sein. Die vierte ist dafür da, den aktuellen
Pfadname + Dateiname zu speichern, damit wir die stat-Funktion
ordnungsgemäß aufrufen können.Als nächstes öffnen wir das Directory '/tmp'.
Danach lesen wir solange aus dem Directory, bis wir an das Ende
angelangen. Die 'Directorys' "." und ".." (Current Directory und
Parent Directory) muessen abgefangen werden. Wuerde man immer "."
lesen, waere es Fatal und man haette eine Endlosschleife. Das selbe
mit "..".
Danach kopieren wir in die Variable 'Path' den Pfad + Dateinamen, so
dass wir so ein Format bekommen: '/PathName/FileName'.
Nun holen wir uns die Informationen ueber diese Datei, werten diese
aus und geben die entsprechende Message aus. Sind wir am Ende des
Directorys angelangt, schließen wir das Directory und das Programm
ist beendet.War ja gar nicht so schwer *g*.
Okay. Dann koennen wir ja jetzt unser Programm schreiben, mit dem
wir ein Directory rekursiv kopieren koennen. Das Programm wird
sicherlich nicht PERFEKT sein. Ich werde auch keine Prüfung von
Rechten hineinbringen oder sowas. Aber das hab ich ja bereits gesagt.Was wir benötigen, ist eine Funktion, die ein Directory durchläuft
und eine Funktion, die Dateien ins neue Directory kopiert.
symbolische Links werde ich abfangen und nicht mitkopieren.Nennen wir die Funktionen Copy und ReadSrcDir (ja, ja, es gibt
bestimmt bessere Namen, aber mir fallen grad keine ein :p ).Die main-Funktion werd ich mir sparen, die kann dann jeder grad
selbst schreiben und die Funktionen mit den noetigen Parametern
aufrufen.Fangen wir mit der Copy-Funktion an. Was muss sie können? Sie muss
eine Datei von einem Directory in das andere kopieren.void Copy(const char *Src,const char *Dst) { size_t ReadBytes; const int Max = 255; char Buffer[Max]; FILE *FileIn; FILE *FileOut; if ( (FileIn = fopen(Src,"r")) == NULL) return; if ( (FileOut = fopen(Dst,"w") ) == NULL) { fclose(FileIn); return; } printf("Kopiere %s nach %s...\n",Src,Dst); //Solange aus Datei gelesen wird while ( (ReadBytes = fread(Buffer,1,Max,FileIn)) > 0) //wird auch in Datei geschrieben if (fwrite(Buffer,1,ReadBytes,FileOut) != ReadBytes) { fclose(FileIn); fclose(FileOut); return; } puts("Fertig!"); fclose(FileIn); fclose(FileOut); }
Soweit so gut. Diese Funktion sollte die Quelldatei ins Zieldirectory
kopieren.Kommen wir nun zur eigentlichen Funktion. Die Funktion, die das
Directory durchlaeuft.void ReadSrcDir(const char *Src,const char *Dst) { /*Wir benötigen natürlich Strukturvariablen von DIR, dirent und stat*/ DIR *Directory; struct dirent *CurrentFile; static struct stat FileInfo; /*Wir benötigen eine Variable, mit der wir einen Absoluten Pfadnamen + Datei speichern*/ char PathName[255]; //Wer weiß wie tief das Directory geht? char PathName2[255]; //Hier solls hin gehn -:) //Directory öffnen if ( (Directory = opendir(Src)) == NULL) return; if (stat(Src,&FileInfo) == -1) { closedir ( Directory ); return; } /*Nun legen wir das Zieldirectory an, falls es schon existiert, geben wir eine entsprechende Meldung aus*/ if (mkdir(Dst,FileInfo.st_mode) == -1) { /*Wir fangen nur diesen Fehler ab. Falls es einen anderen Fehler gibt, beenden wir das Programm. Das kann natuerlich so haendeln wie er will*/ if ( errno == EEXIST ) puts("Directory existiert bereits"); else { closedir(Directory); return; } } else printf ( "Directory %s anlegen\n", Dst ); //Nun fangen wir an, das Directory zu lesen while ( (CurrentFile = readdir(Directory)) != NULL) { /*Prüfen, ob es sich um das Current Directory (".") oder um das Parent Directory ("..") handelt. Wenn ja, dann passiert nichts.*/ if (strcmp(CurrentFile->d_name,".") && strcmp(CurrentFile->d_name,"..")) { /*Nun bauen wir uns den Pfad zusammen, damit wir mit der stat-Funktion die Attribute erfragen können*/ strcpy(PathName,Src); strcat(PathName,"/"); strcat(PathName,CurrentFile->d_name); if (stat(PathName,&FileInfo) == -1) { closedir ( Directory ); return; } /*Handelt es sich um ein Verzeichnis?*/ if (S_ISDIR(FileInfo.st_mode)) { strcpy(PathName2,Dst); strcat(PathName2,"/"); strcat(PathName2,CurrentFile->d_name); ReadSrcDir(PathName,PathName2); } /*oder handelt es sich um eine regulaere Datei?*/ else if ( S_ISREG ( FileInfo.st_mode ) ) { strcpy(PathName2,Dst); strcat(PathName2,"/"); strcat(PathName2,CurrentFile->d_name); Copy(PathName,PathName2); } } } //end while } //end ReadSrcDir
So, das wars auch schon. Hoffe ich hab keine Fehler eingebaut, sonst
korrigiert mich einfach. Ist leider nicht sehr einfach, den Über-
blick zu behalten, da das Textfeld hier nicht so breit ist. Egal,
ich hoffe das Prinzip ist klar geworden.mfg
v RC++
Wer unter C++ Objekt Orientiert programmieren will und sich nicht mit der POSIX C API abmühen möchte, sollte sich folgende Librarys angucken- Boost::Filesystem - ein Teil der Boost Library (Portabel für viele Systeme)
- More - more::io enthält Klassen und Funktionen zum Umgang mit Dateien (nur für POSIX Systeme)