Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.

Channels ▼


Recursive Directory Search in C#

Test Drive

That's probably enough talk about the design. Let's now take a look at the library in action. We've already seen the Windows Font file search, so now let's look at some of the other simple examples that are included with the recls 100% .NET distribution. (For brevity, I'm going to elide the command-line argument handling and other non-relevant aspects here. Check the distribution for the full program listings.)


This example (Listing 12) finds all the empty, accessible subdirectories of the current directory.

SearchOptions all = SearchOptions.IncludeHidden
                  | SearchOptions.IncludeSystem
                  | SearchOptions.IgnoreInaccessibleNodes;

foreach(IEntry directory in FileSearcher.Search(null, null
                               , SearchOptions.Directories | all))
  bool fileFound = false;
  foreach(IEntry file in FileSearcher.Search(directory.Path, null
                               , SearchOptions.Files | all))
    fileFound = true;

Listing 12: Searching for empty directories.


This example shows the total sizes of all immediate sub-directories. It is similar to the one above, but it does not recurse.

foreach(IEntry entry in FileSearcher.Search(null, null
  , SearchOptions.Directories, 0))
  Console.WriteLine("{0} : {1}", entry
    , FileSearcher.CalculateDirectorySize(entry));

This is actually a really useful tool when you're trying to find where the space is being consumed on a drive. It can also be written in a single statement:

FileSearcher.Search(null, null,
  SearchOptions.Directories, 0
).ForEach((e) => Console.WriteLine("{0} : {1}", e
            , FileSearcher.CalculateDirectorySize(e)));


This example (Listing 13) uses the exception handler to list all the inaccessible sub-directories.

FileSearcher.Search(null, null
  , SearchOptions.Directories |
    SearchOptions.IncludeHidden |
  , FileSearcher.UnrestrictedDepth, 
  (string directory, int depth) =>
    Trace.WriteLine("searching " + directory + " [" + depth + "]");
    return ProgressHandlerResult.Continue;
  (path, x) =>
    Console.WriteLine("could not access {0}: {1}", path, x.Message);
    return ExceptionHandlerResult.ConsumeExceptionAndContinue;
).ForEach((e) => e = null);

Listing 13: Searching for inaccessible directories.

The hidden and system flags are specified to ensure the best chance of running into inaccessible directories. For good measure, I have it perform some rudimentary diagnostic logging by specifying a progress handler that traces the directory and depth. Also, note the curious lambda expression in the ForEach() call. This is the best I could think of to give a no-op, since we don't need to do anything with the search results, just have it iterate over all the elements accessible in the IEnumerable<IEntry> instance returned from FileSearcher.Search().

When run on my work drive, I get the following output:

could not access H:\dev\bin\hidden\inaccessible\: Access to the path 'H:\dev\bin\hidden\inaccessible' is denied.
could not access H:\dev\bin\hidden\inaccessible\: Access to the path 'H:\dev\bin\hidden\inaccessible' is denied.
could not access H:\System Volume Information\: Access to the path 'H:\System Volume Information' is denied.
could not access H:\System Volume Information\: Access to the path 'H:\System Volume Information' is denied.


The final directory-oriented example (Listing 14) lists the number of files contained in each directory.

foreach(IEntry dir in FileSearcher.Search(null, null
  , SearchOptions.Directories))
  int n = 0;
  foreach(IEntry file in FileSearcher.Search(dir.Path, null
    , SearchOptions.Files, 0))
  Console.WriteLine("{0} has {1} file(s)", dir.SearchRelativePath, n);

Listing 14: Directory contents frequency analysis.


This example (Listing 15) finds the largest file matching the given pattern(s). I'm including the full listing to illustrate one way of processing command-line arguments into multi-part patterns. (Please note: it's not the best way of handling command-line arguments, but I didn't want to introduce any more dependencies or complexities into the examples.)

static void Main(string[] args)
  string directory = null;
  List<string> patterns = new List<string>();
  foreach(string arg in args)
    if(0 != arg.Length && '-' == arg[0])
        case "--help":
          Console.Error.WriteLine("FindLargestMatchingFile: unrecognised argument {0}; use --help for usage", arg);
      if(null == directory && arg.IndexOfAny(new char[] { '?', '*' }) < 0)
        directory = arg;
        if(arg.IndexOfAny(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar } ) >= 0)
          Console.Error.WriteLine("invalid pattern: {0}", arg);
  if(0 == patterns.Count)
  IEntry largest = null;
  foreach(IEntry entry in FileSearcher.Search(directory
    , String.Join("|", patterns.ToArray())
    , SearchOptions.None))
    if(null == largest || largest.Size < entry.Size)
      largest = entry;
  if(null == largest)
    Console.Out.WriteLine("no matching entries found");
    Console.Out.WriteLine("largest entry is {0}, which is {1} bytes"
      , largest.SearchRelativePath, largest.Size);

Listing 15: Find largest matching file.

FindCertainSmallExecutables (with LINQ)

This example (Listing 16) finds (the search-relative path of) executable modules that are smaller than 10k and read-only, and uses LINQ.

var files = FileSearcher.Search(null, "*.exe|*.dll", SearchOptions.Files);

var modules = from file in files
              where file.Size < 10240 && file.IsReadOnly
              select file.SearchRelativePath;

foreach(var module in modules)
  Console.WriteLine("module: {0}", module);

Listing 16: Find small executables using LINQ.


The last example (Listing 17) illustrates the use of Stat() to elicit information about a single filesystem entity. Once again, I'll show the full listing.

static void Main(string[] args)
  string path = Assembly.GetEntryAssembly().Location;
  if(0 != args.Length)
    path = args[0];
  IEntry entry = FileSearcher.Stat(path);
  if(null == entry)
    Console.Error.WriteLine("file not found");
    Console.WriteLine("{0,20}:\t{1}", "Path", entry.Path);
    Console.WriteLine("{0,20}:\t{1}", "SearchRelativePath", entry.SearchRelativePath);
    Console.WriteLine("{0,20}:\t{1}", "Drive", entry.Drive);
    Console.WriteLine("{0,20}:\t{1}", "DirectoryPath", entry.DirectoryPath);
    Console.WriteLine("{0,20}:\t{1}", "Directory", entry.Directory);
    Console.WriteLine("{0,20}:\t{1}", "SearchDirectory", entry.SearchDirectory);
    Console.WriteLine("{0,20}:\t{1}", "UncDrive", entry.UncDrive);
    Console.WriteLine("{0,20}:\t{1}", "File", entry.File);
    Console.WriteLine("{0,20}:\t{1}", "FileName", entry.FileName);
    Console.WriteLine("{0,20}:\t{1}", "FileExtension", entry.FileExtension);
    Console.WriteLine("{0,20}:\t{1}", "CreationTime", entry.CreationTime);
    Console.WriteLine("{0,20}:\t{1}", "ModificationTime", entry.ModificationTime);
    Console.WriteLine("{0,20}:\t{1}", "LastAccessTime", entry.LastAccessTime);
    Console.WriteLine("{0,20}:\t{1}", "LastStatusChangeTime", entry.LastStatusChangeTime);
    Console.WriteLine("{0,20}:\t{1}", "Size", entry.Size);
    Console.WriteLine("{0,20}:\t{1}", "Attributes", entry.Attributes);
    Console.WriteLine("{0,20}:\t{1}", "IsReadOnly", entry.IsReadOnly);
    Console.WriteLine("{0,20}:\t{1}", "IsDirectory", entry.IsDirectory);
    Console.WriteLine("{0,20}:\t{1}", "IsUnc", entry.IsUnc);
    Console.WriteLine("{0,20}:\t[{1}]", "DirectoryParts", String.Join(", ", entry.DirectoryParts.ToArray())); // Assumes "using System.Linq"

Listing 17: Stat() a file.

When run on my development system, I get the following output:

                Path:   H:\freelibs\recls\100\recls.net\examples\StatASolutionFile\bin\Debug\StatASolutionFile.exe
  SearchRelativePath:   StatASolutionFile.exe
               Drive:   H:
       DirectoryPath:   H:\freelibs\recls\100\recls.net\examples\StatASolutionFile\bin\Debug\
           Directory:   \freelibs\recls\100\recls.net\examples\StatASolutionFile\bin\Debug\
     SearchDirectory:   H:\freelibs\recls\100\recls.net\examples\StatASolutionFile\bin\Debug\
                File:   StatASolutionFile.exe
            FileName:   StatASolutionFile
       FileExtension:   .exe
        CreationTime:   3/10/2009 6:53:23 AM
    ModificationTime:   3/10/2009 8:05:59 AM
      LastAccessTime:   3/10/2009 8:14:35 AM
LastStatusChangeTime:   3/10/2009 8:05:59 AM
                Size:   7168
          Attributes:   Archive, Compressed
          IsReadOnly:   False
         IsDirectory:   False
               IsUnc:   False
      DirectoryParts:   [\, freelibs\, recls\, 100\, recls.net\, examples\, StatASolutionFile\, bin\, Debug\]

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.