Reputation: 971
I have the following code to recursively search for files through a directory, which returns a list of all xml files to me. All works well, except that xml files in the root directory are not included in the list.
I understand why, since the first thing it does is get the directories in the root, then get files, thus missing the GetFiles() call on the root. I tried including the GetFiles() call prior to the foreach, but the results are not as I expect.
public static ArrayList DirSearch(string sDir)
{
try
{
foreach (string d in Directory.GetDirectories(sDir))
{
foreach (string f in Directory.GetFiles(d, "*.xml"))
{
string extension = Path.GetExtension(f);
if (extension != null && (extension.Equals(".xml")))
{
fileList.Add(f);
}
}
DirSearch(d);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return fileList;
}
My directory structure is something like:
RootDirectory
test1.0.xml
test1.1.xml
test1.2.xml
2ndLevDir
test2.0.xml
test2.1.xml
3rdLevDir
test3.0.xml
test3.1.xml
Code returns:
test2.0.xml
test2.1.xml
test3.0.xml
test3.1.xml
I would like to return every file including:
test1.0.xml
test1.1.xml
test1.2.xml
Not very well verse with recursion. Any pointers would be greatly appreciated.
Upvotes: 94
Views: 221541
Reputation: 25013
You could use this overload of Directory.GetFiles which searches subdirectories for you, for example:
string[] files = Directory.GetFiles(sDir, "*.xml", SearchOption.AllDirectories);
Only one extension can be searched for like that, but you could use something like:
var extensions = new List<string> { ".txt", ".xml" };
string[] files = Directory.GetFiles(sDir, "*.*", SearchOption.AllDirectories)
.Where(f => extensions.IndexOf(Path.GetExtension(f)) >= 0).ToArray();
to select files with the required extensions (N.B. that is case-sensitive for the extension).
If you want a case-insensitive extension check:
var extensions = new List<string> { ".txt", ".xml" };
var files = Directory.GetFiles(sDir, "*.*", SearchOption.AllDirectories)
.Where(f => extensions
.Any(extn => string.Compare(Path.GetExtension(f), extn, StringComparison.InvariantCultureIgnoreCase) == 0))
.ToArray();
In some cases it can be desirable to enumerate over the files with the Directory.EnumerateFiles Method:
foreach(string f in Directory.EnumerateFiles(sDir, "*.xml", SearchOption.AllDirectories))
{
// do something
}
Consult the documentation for exceptions which can be thrown, such as UnauthorizedAccessException if the code is running under an account which does not have appropriate access permissions.
If the UnauthorizedAccessException is a problem, then please see the fine answers at Directory.EnumerateFiles => UnauthorizedAccessException.
Upvotes: 200
Reputation: 677
I tried some of the other solutions listed here, but during unit testing the code would throw exceptions I wanted to ignore. I ended up creating the following recursive search method that will ignore certain exceptions like PathTooLongException and UnauthorizedAccessException.
private IEnumerable<string> RecursiveFileSearch(string path, string pattern, ICollection<string> filePathCollector = null)
{
try
{
filePathCollector = filePathCollector ?? new LinkedList<string>();
var matchingFilePaths = Directory.GetFiles(path, pattern);
foreach(var matchingFile in matchingFilePaths)
{
filePathCollector.Add(matchingFile);
}
var subDirectories = Directory.EnumerateDirectories(path);
foreach (var subDirectory in subDirectories)
{
RecursiveFileSearch(subDirectory, pattern, filePathCollector);
}
return filePathCollector;
}
catch (Exception error)
{
bool isIgnorableError = error is PathTooLongException ||
error is UnauthorizedAccessException;
if (isIgnorableError)
{
return Enumerable.Empty<string>();
}
throw error;
}
}
Upvotes: 2
Reputation: 31
For file and directory search purpose I would want to offer use specialized multithreading .NET library that possess a wide search opportunities and works very fast.
All information about library you can find on GitHub: https://github.com/VladPVS/FastSearchLibrary
If you want to download it you can do it here: https://github.com/VladPVS/FastSearchLibrary/releases
If you have any questions please ask them.
It is one demonstrative example how you can use it:
class Searcher
{
private static object locker = new object();
private FileSearcher searcher;
List<FileInfo> files;
public Searcher()
{
files = new List<FileInfo>(); // create list that will contain search result
}
public void Startsearch()
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
// create tokenSource to get stop search process possibility
searcher = new FileSearcher(@"C:\", (f) =>
{
return Regex.IsMatch(f.Name, @".*[Dd]ragon.*.jpg$");
}, tokenSource); // give tokenSource in constructor
searcher.FilesFound += (sender, arg) => // subscribe on FilesFound event
{
lock (locker) // using a lock is obligatorily
{
arg.Files.ForEach((f) =>
{
files.Add(f); // add the next received file to the search results list
Console.WriteLine($"File location: {f.FullName}, \nCreation.Time: {f.CreationTime}");
});
if (files.Count >= 10) // one can choose any stopping condition
searcher.StopSearch();
}
};
searcher.SearchCompleted += (sender, arg) => // subscribe on SearchCompleted event
{
if (arg.IsCanceled) // check whether StopSearch() called
Console.WriteLine("Search stopped.");
else
Console.WriteLine("Search completed.");
Console.WriteLine($"Quantity of files: {files.Count}"); // show amount of finding files
};
searcher.StartSearchAsync();
// start search process as an asynchronous operation that doesn't block the called thread
}
}
Upvotes: 0
Reputation: 189
Using EnumerateFiles to get files in nested directories. Use AllDirectories to recurse throught directories.
using System;
using System.IO;
class Program
{
static void Main()
{
// Call EnumerateFiles in a foreach-loop.
foreach (string file in Directory.EnumerateFiles(@"c:\files",
"*.xml",
SearchOption.AllDirectories))
{
// Display file path.
Console.WriteLine(file);
}
}
}
Upvotes: 1
Reputation: 837886
You should have the loop over the files either before or after the loop over the directories, but not nested inside it as you have done.
foreach (string f in Directory.GetFiles(d, "*.xml"))
{
string extension = Path.GetExtension(f);
if (extension != null && (extension.Equals(".xml")))
{
fileList.Add(f);
}
}
foreach (string d in Directory.GetDirectories(sDir))
{
DirSearch(d);
}
Upvotes: 4
Reputation: 2028
you can do something like this:
foreach (var file in Directory.GetFiles(MyFolder, "*.xml", SearchOption.AllDirectories))
{
// do something with this file
}
Upvotes: 2
Reputation: 48949
You will want to move the loop for the files outside of the loop for the folders. In addition you will need to pass the data structure holding the collection of files to each call of the method. That way all files go into a single list.
public static List<string> DirSearch(string sDir, List<string> files)
{
foreach (string f in Directory.GetFiles(sDir, "*.xml"))
{
string extension = Path.GetExtension(f);
if (extension != null && (extension.Equals(".xml")))
{
files.Add(f);
}
}
foreach (string d in Directory.GetDirectories(sDir))
{
DirSearch(d, files);
}
return files;
}
Then call it like this.
List<string> files = DirSearch("c:\foo", new List<string>());
Update:
Well unbeknownst to me, until I read the other answer anyway, there is already a builtin mechanism for doing this. I will leave my answer in case you are interested in seeing how your code needs to be modified to make it work.
Upvotes: 3
Reputation: 460018
This returns all xml-files recursively :
var allFiles = Directory.GetFiles(path, "*.xml", SearchOption.AllDirectories);
Upvotes: 52
Reputation: 4232
You are creating three lists, instead of using one (you don't use the return value of DirSearch(d)
). You can use a list as a parameter to save the state:
static void Main(string[] args)
{
var list = new List<string>();
DirSearch(list, ".");
foreach (var file in list)
{
Console.WriteLine(file);
}
}
public static void DirSearch(List<string> files, string startDirectory)
{
try
{
foreach (string file in Directory.GetFiles(startDirectory, "*.*"))
{
string extension = Path.GetExtension(file);
if (extension != null)
{
files.Add(file);
}
}
foreach (string directory in Directory.GetDirectories(startDirectory))
{
DirSearch(files, directory);
}
}
catch (System.Exception e)
{
Console.WriteLine(e.Message);
}
}
Upvotes: 3
Reputation: 7961
Try the following method:
public static IEnumerable<string> GetXMLFiles(string directory)
{
List<string> files = new List<string>();
try
{
files.AddRange(Directory.GetFiles(directory, "*.xml", SearchOption.AllDirectories));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return files;
}
Upvotes: 3