Laden...

[Snippet] Verzeichnisse und Dateien rekursiv durchlaufen

Erstellt von herbivore vor 15 Jahren Letzter Beitrag vor 10 Jahren 44.944 Views
herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 15 Jahren
[Snippet] Verzeichnisse und Dateien rekursiv durchlaufen

Beschreibung:

mit .NET 2.0 wurde die Methode

Directory.GetFiles (strStartVerzeichnis, "*", SearchOption.AllDirectories) 

eingeführt, die durch die Angabe SearchOption.AllDirectories nicht nur die Dateien in strStartVerzeichnis liefert, sondern auch rekursiv alle Dateien in allen Unterverzeichnissen. Leider liefert die Methode kein Ergebnis sondern eine Exception, sobald auch nur auf ein Unterverzeichnis kein Zugriff besteht. Deshalb ist die Methode in der Praxis nur sehr eingeschränkt nutzbar und man muss die Rekursion doch selbst ausprogrammieren.

Um die mit .NET 4.0 eingeführte Methode Directory.EnumerateFiles steht es in Bezug auf SearchOption.AllDirectories nicht viel besser, wie weiter unten im Thread diskutiert wird.

Natürlich gibt es noch andere Gründe, auf eine eigene rekursive Methode zurückzugreifen, z.B. wenn man das Dateisystem in einer bestimmten Reihenfolge durchlaufen will oder man nur in bestimmte Unterverzeichnisse berücksichtigen will oder nur Unterverzeichnisse bis zu einer bestimmten Tiefe.

Deshalb hier ein Rahmen für eine solche, rekursive Methode, den man nach eigenem Gutdünken ausfüllen kann:


public static void TraverseFileSystem (String strDir) {
   try {
      // 1. Für alle Dateien im aktuellen Verzeichnis
      foreach (String strFile in Directory.GetFiles (strDir)) {
         // 1a. Statt Console.WriteLine hier die gewünschte Aktion
         Console.WriteLine (strFile);
      }

      // 2. Für alle Unterverzeichnisse im aktuellen Verzeichnis
      foreach (String strSubDir in Directory.GetDirectories (strDir)) {
         // 2a. Statt Console.WriteLine hier die gewünschte Aktion
         Console.WriteLine (strSubDir);

         // 2b. Rekursiver Abstieg
         TraverseFileSystem (strSubDir);
      }
   }
   catch (Exception) {
      // 3. Statt Console.WriteLine hier die gewünschte Aktion
      Console.WriteLine ("error: " + strDir);
   }
}

**
Anmerkungen:**

  • Je nach Reihenfolge, in der man das Dateisystem durchlaufen will, kann man Punkt 1 und Punkt 2 sowie Punkt 2a und Punkt 2b vertauschen. Außerdem kann man Punkt 2a aus der Schleife heraus (vor, zwischen oder hinter 1 und 2) verschieben und dabei strSubDir durch strDir ersetzen. *Die Rückgabe der Aufrufe von Directory.GetFiles (strDir) und/oder Directory.GetDirectories (strDir) kann man vor dem Durchlaufen in jede gewünschte Reihenfolge bringen. Klassisch z.B. mit Array.Sort und einem eigenen IComparer<T> oder eleganter per Linq mit OrderBy direkt im Schleifenkopf.
  • Kann die Aktion bei Punkt 1a und 2a zu Exceptions führen, sollte man ein zusätzliches try/catch innerhalb der jeweiligen Schleife verwenden. Statt des allumfassenden try/catch, kann man auch gezielt die einzelnen Operation, die Exceptions liefern können, mit try/catch umschließen. Dazu muss man ggf. Directory.GetFiles/GetDirectories vor dem Schleifenkopf aufrufen und das Ergebnis in einer Variable zwischenspeichern.
  • Die Aktionen bei 1a, 2a und/oder 3 können entfallen, wenn man sie nicht benötigt. Will man eine Aktion z.B. nur für die Dateien durchführen, führt man diese bei 1a aus und 2a und 3 entfallen.
  • Natürlich kann man den rekursiven Aufruf in 2b durch eine if-Abfrage auch von bestimmten Bedingungen abhängig machen, z.B. von bestimmten Verzeichnisnamen oder der aktuellen Rekursionstiefe.
  • Um das Snippet robust zu machen und eine Endlosrekursion zu vermeiden, sollte man insbesondere keine Hardlinks/Junctions besuchen, die auf ein Oberverzeichnis verlinken, siehe den Beitrag von winSharp93 weiter unten. *Statt die Aktionen direkt auszuführen, kann man natürlich auf der obersten Rekursionsebene eine List<String> erstellen, die gefundenen Dateien darin sammeln und die Liste am Ende zurückgeben. *Statt die Aktionen direkt auszuführen, kann man natürlich auch ein oder mehrere Events definieren ([FAQ] Eigenen Event definieren), die anstelle der jeweiligen Aktion gefeuert werden. In denen eigenen EventArgs wird sinnvollerweise mindestens der jeweilige Datei- oder Verzeichnisname übergeben. Die eigentlichen Aktionen werden dann in den EventHandlern ausgeführt. *Ob man für das Snippet die Get- oder die Enumerate-Methoden verwendet, ist in den meisten Fällen im wesentlichen Geschmackssache.
  • Will man die Größe oder andere Eigenschaften der der Dateien ermitteln, kann man an Punkt 1 folgende Schleife verwenden:

// 1. Für alle Dateien im aktuellen Verzeichnis
foreach (FileInfo fi in new DirectoryInfo (strDir).GetFiles ()) {
   // 1a. Statt Console.WriteLine hier die gewünschte Aktion
   Console.WriteLine (fi.FullName);
}

**
Variante mit FileInfo und DirectoryInfo**

Hier folgt noch eine Variante, bei der statt der Klassen File und Directory die Klassen FileInfo und DirectoryInfo verwendet werden. Das hat den Vorteil, dass man gleich die meisten Informationen über Dateien und Verzeichnisse parat hat, z.B. das Datum des letzten Zugriffs. Auf der anderen Seite müssen diese Informationen ja ermittelt und in die Objekte übertragen werden. Dadurch sind Laufzeit und Speicherverbrauch etwas größer. Zumindest auf lokalen Platten sind die Unterschiede praktisch so gering, dass es im Wesentlichen Geschmackssache ist, für welche Variante man sich entscheidet.

Die Anmerkungen von oben gelten für diese Variante genauso.


public static void TraverseFileSystem (String strDir) {

   // 0. Einstieg in die Rekursion auf oberster Ebene
   TraverseFileSystem (new DirectoryInfo (strDir));
}

private static void TraverseFileSystem (DirectoryInfo di) {
   try {
      // 1. Für alle Dateien im aktuellen Verzeichnis
      foreach (FileInfo fi in di.GetFiles ()) {
         // 1a. Statt Console.WriteLine hier die gewünschte Aktion
         Console.WriteLine (fi.FullName);
      }

      // 2. Für alle Unterverzeichnisse im aktuellen Verzeichnis
      foreach (DirectoryInfo diSub in di.GetDirectories ()) {
         // 2a. Statt Console.WriteLine hier die gewünschte Aktion
         Console.WriteLine (diSub.FullName);

         // 2b. Rekursiver Abstieg
         TraverseFileSystem (diSub);
      }
   }
   catch (Exception) {
      // 3. Statt Console.WriteLine hier die gewünschte Aktion
      Console.WriteLine ("error: " + di.FullName);
   }
}

**
Siehe auch**

MaskMatch Klasse zum durchsuchen mehrerer Verzeichnisebenen

Schlagwörter: Datei, Dateien, Dateiname, Dateinamen, File, Files, Filename, Filenames, File Name, File Names, Verzeichnis, Verzeichnisse, Verzeichnisname, Verzeichnisnamen, Directory, Directories, Directoryname, Directorynames, Directory Name, Directory Names, Unterverzeichnis, Unterverzeichnisse, Subdirectory, Subdirectories, Dateisystem, Filesystem, File System, FAT, FAT32, NTFS rekursiv, rekursive, rekursiven, rekursives, Rekursion, recusive, durchlaufen, traversieren, auflisten, aufzählen, suchen, durchsuchen, Ereignis, Ereignisse, Event, Events, ereignisbasiert, ereignisbasierte, ereignisorientiert, ereignisorientierte, ereignisgesteuert, ereignisgesteuerte, List, Lists, Liste, Listen, Reihenfolge, sortiert, sortierte, sortieren, Sortierung, 1000 Worte

4.931 Beiträge seit 2008
vor 15 Jahren
Hinweis von herbivore vor 9 Jahren

Mit Verknüpfung in dem folgenden MSDN-Zitat sind wohl keine .lnk-Dateien, sondern Hardlinks oder Junktions gemeint.

Es gibt noch einen anderen Grund, der gegen die AllDirectories-Methode spricht:

Hinweis:
Wenn Sie AllDirectories in der Suche auswählen und die Verzeichnisstruktur eine Verknüpfung enthält, sodass eine Schleife entsteht, befindet sich der Suchvorgang in einer Endlosschleife.

Würde es denn deiner Meinung nach große Performanceeinbußen geben, wenn man die Aktionen bei deinem Code als Delegates definieren würde?
Dann hätte man eine allgemeingültige Methode (oder 2, wenn man die Reihenfolge der beiden Aktionen 1 und 2 vertauscht), ohne immer wieder den Code kopieren zu müssen.

Gelöschter Account
vor 15 Jahren

das mit dem delegate ist gut möglich nur nicht sinnvoll. man kann ja nicht wissen, was der entwickler machen möchte.

übrigens hätte man bei dieser methode ebenfalls ein problem mit verknüpfungen auf einem ordner. der ausweg ist, alle bereits durchlaufenen ordner zu merken und ggf. nicht mehr zu durchlaufen. aber das ist auch eine anforderung, die nicht oft benötigt wird und ist aus diesem grund auch niht in der frameworkversion enthalten.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 15 Jahren

Hallo Th69,

Würde es denn deiner Meinung nach große Performanceeinbußen geben, wenn man die Aktionen bei deinem Code als Delegates definieren würde?

nein, das aufwändige sind die Festplattenzugriffe. Ein zusätzlicher Delegaten-Aufruf pro Datei sollte nicht wirklich was ändern. Das was du meinst, kann man also durchaus machen, auch wenn ich für Events statt "einfachen" Delegaten plädiere. Es war nur nicht mein Ziel mit diesem Snippet. Ich sehe das Snippet als Vorlage für eine eigene, individuell zu schreibende Methode. Daher auch die Anmerkungen, welche Änderungsmöglichkeiten bestehen. Ich habe deine Anregung aber oben als eine weitere Änderungsmöglichkeit aufgenommen.

Wenn Sie AllDirectories in der Suche auswählen und die Verzeichnisstruktur eine Verknüpfung enthält, sodass eine Schleife entsteht, befindet sich der Suchvorgang in einer Endlosschleife.

Mit Verknüpfung wird hier wohl nicht nicht Verküpfung im Sinne von .lnk-Dateien gemeint sein, sondern Hardlinks. Und wer Hardlinks auf übergeordnete Verzeichnisse anlegt, gehört erschossen 🙂 Im Sinne der Robustheit wäre es jedoch sinnvoll, wenn ein Programm damit umgehen kann. Leider bleibt einem dazu wohl wirklich nichts anders, als was JAck30lena vorgeschlagen hat. [EDIT]Am besten mit dem Vorschlag von winSharp93 weiter unten.[/EDIT]

herbivore

630 Beiträge seit 2007
vor 15 Jahren

Wusste garnicht das bei NTFS Hardlinks möglich sind. Wieder was gelernt 😄

To understand recursion you must first understand recursion

http://www.ilja-neumann.com
C# Gruppe bei last.fm

3.971 Beiträge seit 2006
vor 15 Jahren

NTFS unterstützt sogar das hart verlinken von Verzeichnissen. Unter http://www.mcseboard.de/tipps-links-5/versteckte-funktionen-ntfs-hardlinks-u-abzweigungspunkte-58903.html gibt es eine sehr schöne kleine Einführung in das Thema

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

5.742 Beiträge seit 2007
vor 15 Jahren

Hallo zusammen,

auch wenn der Thread nicht gerade brandneu ist:

Leider bleibt einem dazu wohl wirklich nichts anders, als was JAck30lena vorgeschlagen hat.

Nein - es geht auch einfacher.

Zuerst kann man prüfen, ob ein Verzeichnis eine Junction ist:


DirectoryInfo info = new DirectoryInfo("/**/");
if ((info.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{ //Es handelt sich um eine Junction
}

Dann hilft Codeproject: Manipulation NTFS Junctions weiter. Der Artikel stellt eine Library vor, mit der man das Ziel einer Junction ermitteln kann. Hat man dieses erst einmal, muss man nur noch prüfen, ob die Junction auf ein übergeordnetes Verzeichnis zeigt.

Leider fehlt mir aber im Moment die Zeit, das ganze wirklich zu testen und einen ordentlichen Beispielcode zu schreiben. Die Idee wollte ich aber trotzdem hier niederschreiben.

M
18 Beiträge seit 2009
vor 12 Jahren
Mit .NET 4 gelöst...

Hallo,

ich habe gerade lange Zeit mich über die besch... Implementierung von GetFiles() geärgert, da bin ich auf diese neue Methode gestoßen: EnumerateFiles(String, SearchOption) (ab .NET4) 😁

Gruß mru.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 12 Jahren

Hallo mru,

erstmal danke für den Hinweis. Leider wird durch die neuen Methoden das eigentliche Problem nicht gelöst. Aber der Reihe nach.

Es gibt sowohl die Methode DirectoryInfo.EnumerateFiles als auch die Methode Directory.EnumerateFiles.

In der MSDN-Doku steht bei beiden Methoden zu lesen:

Die EnumerateFiles-Methode und die GetFiles-Methode unterscheiden sich wie folgt: Wenn Sie EnumerateFiles verwenden, können Sie anfangen, die Auflistung von Namen aufzulisten, bevor die ganze Auflistung zurückgegeben wird; wenn Sie GetFiles verwenden, müssen Sie warten, bis das ganze Array von Namen zurückgegeben wird, bevor Sie auf das Array zugreifen können. Wenn Sie daher mit vielen Dateien und Verzeichnissen arbeiten, kann EnumerateFiles effizienter sein.

Der Unterschied besteht also nur darin, ob die Ergebnisse auf einen Rutsch oder Datei für Datei geliefert werden. Das eigentliche Problem beim rekursiven GetFiles ist aber ein anderes. Ich habe es ganz oben beschrieben:

Leider liefert die Methode kein Ergebnis sondern eine Exception, sobald auch nur auf ein Unterverzeichnis kein Zugriff besteht.

Und dieses Problem löst auch EnumerateFiles nicht wirklich. Zwar kommt die Exception nicht sofort, sondern erst wenn das "böse" Verzeichnis erreicht ist (vielen Dank an gfoidl, der das getestet hat), es bleibt aber das Problem, dass die Auflistung mit einer Exception abbricht, sobald auch nur auf ein Unterverzeichnis kein Zugriff besteht. Insofern ist auch EnumerateFiles keine echte Lösung.

Aber selbst wenn auf alle zu durchsuchenden Verzeichnisse Zugriff besteht, hat man bei EnumerateFiles zumindest keinen Einfluss auf die Reihenfolge, in der die Dateien aufgelistet werden. Wenn man mein rekursives Snippet verwendet, kann man durch die im Text beschriebenen Änderungen die Reihenfolge den eigenen Vorstellungen und Anforderungen anpassen.

Und auch wenn man Dateien und Verzeichnisse gleichzeitig/verzahnt auflisten will, helfen weder die rekursiven GetFiles/EnumerateFiles noch die rekursiven GetDirectories/EnumerateDiectories alleine, sondern nur eine eigene rekursive Implementierung entsprechend des obigen Snippets.

EnumerateFiles bringt nur dann spürbare Vorteile bringt, wenn man wiederholt vor hat, die Enumeration abzubrechen, bevor alle Dateien durchlaufen sind. Oder wenn man Verzeichnisse mit extrem vielen Dateien hat. Ansonsten kann man weiterhin GetFiles verwenden. GetFiles hat aus meiner Sicht den Vorteil, dass es das komplette Ergebnis liefert oder gar keins (SecurityException). Bei EnumerateFiles können Exceptions wie gesagt mitten in der Enumeration auftreten. Und GetFiles ist sowieso angezeigt, wenn man die Dateien vor der Verarbeitung sortieren will oder muss. Es kommt also auf die konkrete Situation an, ob die eine oder die andere Methode besser geeignet ist.

Das Gesagte gilt für EnumerateDirectories analog.

herbivore

M
18 Beiträge seit 2009
vor 12 Jahren

Hallo zurück,
in der Tat. Ich habe das eben durchexerziert und beim Erreichen des "System Volume Information" Verzeichnisse bricht es in der Tat ab.
Ich verstehe nicht, warum MS es nicht gelingt, diese Methode als Option ohne Exception zu implementieren und man dies selbst machen muss 😦

Gruß mru

Hinweis von Abt vor 10 Jahren

Themen zusammengefügt

E
10 Beiträge seit 2013
vor 10 Jahren
Rekursiv alle zugreifbaren Dateien eines Ordners + Unterordner anzeigen lassen

Ich glaube so etwas gibt es hier noch nicht, falls doch: sorry, mein fehler...
Beschreibung:

Per

var allfiles = System.IO.Directory.GetFiles(@"C:\MyDirectory",   "*.*",   System.IO.SearchOption.AllDirectories);

kann man sich ja ganz einfach alle Dateien eines Ordners samt der Unterordner anzeigen lassen, jedoch gibt es ein Problem, falls dort ein Ordner sein solle, für den man keine Zugriffsrechte besitzt. In diesem Fall wird eine

UnauthorizedAccessException

geworfen und keine weiteren Dateien mehr ausgegeben.
Für diesen Fall hab ich eine kleine Rekursive Methode entwickelt.

Einfach mit

 List<string> files = GetAllAccessibleFiles(@"C:\MyDirectory");

aufrufen.

Snippet:


public static List<string> GetAllAccessibleFiles(string rootPath, List<string> alreadyFound = null)
        {
            if (alreadyFound == null)
                alreadyFound = new List<string>();
            DirectoryInfo di = new DirectoryInfo(rootPath);
            var dirs = di.EnumerateDirectories();
            foreach (DirectoryInfo dir in dirs)
            {
                if (!((dir.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden))
                {
                    alreadyFound = GetAllAccessibleFiles(dir.FullName, alreadyFound);
                }
            }

            var files = Directory.GetFiles(rootPath);
            foreach (string s in files)
            {
                alreadyFound.Add(s);
            }

            return alreadyFound;
        }

Schlagwörter: rekursiv dateien ordner unterordner auflisten UnauthorizedAccessException

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 10 Jahren

Hallo zusammen,

dadurch, dass in dem Snippet von emsch der rekursive Abstieg zuerst erfolgt, landen die Dateien in einer unüblichen Reihenfolge in der Liste (bottom first). Wenn man das Füllen der Liste und den rekursiven Abstieg vertauscht, entspricht die Reihenfolge dem gewohnten Bild.

EDIT: Außerdem ist es inkonsequent, dass versteckte Ordner (und ihre Unterordner) nicht durchlaufen werden, aber versteckte Dateien in den durchlaufenen Ordnern aufgelistet werden. Vermutlich liegt die Ursache in der von Th69 im folgenden Beitrag angesprochenen Verwechslung.

BTW: Die Zuweisung an alreadyFound bei rekursiven Abstieg ist nicht erforderlich.

herbivore

4.931 Beiträge seit 2008
vor 10 Jahren

Hallo herbivore, hallo emsch,

die Abfrage auf versteckte Dateien hat doch ersteinmal nichts direkt mit den Zugriffsrechten zu tun (auch wenn einige Systemordner als versteckt markiert sind), daher sollte man zusätzlich immer noch diese Exception abfangen.