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
    handelt

    S_ISDIR()
    Liefert einen Wert ungleich 0, wenn es sich um ein Directory handelt

    S_ISCHR()
    Liefert einen Wert ungleich 0, wenn es sich um ein zeichenorientiertes
    Geraet handelt

    S_ISBLK()
    Liefert einen Wert ungleich 0, wenn es sich um ein blockorientiertes
    Geraet handelt

    S_ISFIFO()
    Liefert einen Wert ungleich 0, wenn es sich um eine Pipe oder FIFO
    handelt

    Ihr 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 Fehler

    Wie 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 R

    C++
    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)

Anmelden zum Antworten