I have a program that iterates through all files from directories and subdirectories, it's working smoothly but there is just a minor issue that my brain can't solve.
The one finding the simplest way to solve it is a genius :)
Here is the code :
int hello(string locat)
{
string[] files = Directory.GetFiles(locat);
string[] dirs = Directory.GetDirectories(locat);
int cpt = 0;
foreach (var file in files)
{
try
{
textBox1.AppendText(file+"\r\n");
cpt++;
textBox2.AppendText(cpt.ToString()+"\r\n");
}
catch { }
}
foreach (string directory in dirs)
{
try
{
cpt += hello(directory);
}
catch { }
}
return cpt;
}
So the problem is that the output of cpt inside textBox2 have a logic behavior but a behavior that is not adequate for my needs
This is how it looks like :
1
2
3
1
2
1
2
...
And I want it to be 1,2,3,4,5,6,7,8,9,...
I tried with EnumerateFiles instead of GetFiles, it was working smoothly too but i got some permissions issue and I'm working on .NET framework for this project
I haven't tried this but you can just make hello take cpt as a parameter.
int hello(string locat, ref int cpt)
{
string[] files = Directory.GetFiles(locat);
string[] dirs = Directory.GetDirectories(locat);
foreach (var file in files)
{
try
{
textBox1.AppendText(file+"\r\n");
cpt++;
textBox2.AppendText(cpt.ToString()+"\r\n");
}
catch { }
}
foreach (string directory in dirs)
{
try
{
hello(directory, ref cpt);
}
catch { }
}
return cpt;
}
Edit:
You need to run it with ref
int cpt = 0;
hello("C:\\", ref cpt);
Here is the output I get if I run it with the following folder structure:
testfolder/
> folder1/
> a.txt
> b.txt
> c.txt
> folder2/
> a.txt
> b.txt
> c.txt
> folder3/
> a.txt
> b.txt
> c.txt
Output:
D:\testfolder\folder1\a.txt
1
D:\testfolder\folder1\b.txt
2
D:\testfolder\folder1\c.txt
3
D:\testfolder\folder2\a.txt
4
D:\testfolder\folder2\b.txt
5
D:\testfolder\folder2\c.txt
6
D:\testfolder\folder3\a.txt
7
D:\testfolder\folder3\b.txt
8
D:\testfolder\folder3\c.txt
9
A variation that avoids ref
int hello(string locat, int counter = 0)
{
string[] files = Directory.GetFiles(locat);
string[] dirs = Directory.GetDirectories(locat);
foreach (var file in files)
{
try
{
textBox2.AppendText(file + "\r\n");
counter++;
textBox2.AppendText(counter.ToString() + "\r\n");
}
catch { }
}
foreach (string directory in dirs)
{
try
{
counter = hello(directory, counter);
}
catch { }
}
return counter;
}
Your variable cpt is locally scoped, so you get a new variable instance for every recursive call. You can instead use a field (and don't increment it based on the result of your recursive call):
int cpt = 0;
void hello(string locat)
{
string[] files = Directory.GetFiles(locat);
string[] dirs = Directory.GetDirectories(locat);
foreach (var file in files)
{
try
{
textBox1.AppendText(file + "\r\n");
cpt++;
textBox2.AppendText(cpt.ToString() + "\r\n");
}
catch { }
}
foreach (string directory in dirs)
{
try
{
hello(directory);
}
catch { }
}
}
This code is not thread-safe.
Related
I need to be able to get all files from a directory and sub directories, but I would like to give the user the option to choose the depth of sub-directories.
I.e., not just current directory or all directories, but he should be able to choose a depth of 1,2,3,4 directories etc.
I've seen many examples of walking through directory trees and none of them seemed to address this issue. And personally, I get confused with recursion... (which I currently use). I am not sure how I would track the depth during a recursive function.
Any help would be greatly appreciated.
Thanks,
David
Here is my current code (which I found here):
static void FullDirList(DirectoryInfo dir, string searchPattern, string excludeFolders, int maxSz, string depth)
{
try
{
foreach (FileInfo file in dir.GetFiles(searchPattern))
{
if (excludeFolders != "")
if (Regex.IsMatch(file.FullName, excludeFolders, RegexOptions.IgnoreCase)) continue;
myStream.WriteLine(file.FullName);
MasterFileCounter += 1;
if (maxSz > 0 && myStream.BaseStream.Length >= maxSz)
{
myStream.Close();
myStream = new StreamWriter(nextOutPutFile());
}
}
}
catch
{
// make this a spearate streamwriter to accept files that failed to be read.
Console.WriteLine("Directory {0} \n could not be accessed!!!!", dir.FullName);
return; // We alredy got an error trying to access dir so dont try to access it again
}
MasterFolderCounter += 1;
foreach (DirectoryInfo d in dir.GetDirectories())
{
//folders.Add(d);
// if (MasterFolderCounter > maxFolders)
FullDirList(d, searchPattern, excludeFolders, maxSz, depth);
}
}
using a maxdepth varibale that could be decremented each recursive call and then you cannot just return once reached the desired depth.
static void FullDirList(DirectoryInfo dir, string searchPattern, string excludeFolders, int maxSz, int maxDepth)
{
if(maxDepth == 0)
{
return;
}
try
{
foreach (FileInfo file in dir.GetFiles(searchPattern))
{
if (excludeFolders != "")
if (Regex.IsMatch(file.FullName, excludeFolders, RegexOptions.IgnoreCase)) continue;
myStream.WriteLine(file.FullName);
MasterFileCounter += 1;
if (maxSz > 0 && myStream.BaseStream.Length >= maxSz)
{
myStream.Close();
myStream = new StreamWriter(nextOutPutFile());
}
}
}
catch
{
// make this a spearate streamwriter to accept files that failed to be read.
Console.WriteLine("Directory {0} \n could not be accessed!!!!", dir.FullName);
return; // We alredy got an error trying to access dir so dont try to access it again
}
MasterFolderCounter += 1;
foreach (DirectoryInfo d in dir.GetDirectories())
{
//folders.Add(d);
// if (MasterFolderCounter > maxFolders)
FullDirList(d, searchPattern, excludeFolders, maxSz, depth - 1);
}
}
Let's start out by refactoring the code a little bit to make its work a little easier to understand.
So, the key exercise here is to recursively return all of the files that match the patterns required, but only to a certain depth. Let's get those files first.
public static IEnumerable<FileInfo> GetFullDirList(
DirectoryInfo dir, string searchPattern, int depth)
{
foreach (FileInfo file in dir.GetFiles(searchPattern))
{
yield return file;
}
if (depth > 0)
{
foreach (DirectoryInfo d in dir.GetDirectories())
{
foreach (FileInfo f in GetFullDirList(d, searchPattern, depth - 1))
{
yield return f;
}
}
}
}
This is just simplified the job of recursing for your files.
But you'll notice that it didn't exclude files based on the excludeFolders parameter. Let's tackle that now. Let's start building FullDirList.
The first line would be
var results =
from fi in GetFullDirList(dir, searchPattern, depth)
where String.IsNullOrEmpty(excludeFolders)
|| !Regex.IsMatch(fi.FullName, excludeFolders, RegexOptions.IgnoreCase)
group fi.FullName by fi.Directory.FullName;
This goes and gets all of the files, restricts them against excludeFolders and then groups all the files by the folders they belong to. We do this so that we can do this next:
var directoriesFound = results.Count();
var filesFound = results.SelectMany(fi => fi).Count();
Now I noticed that you were counting MasterFileCounter & MasterFolderCounter.
You could easily write:
MasterFolderCounter+= results.Count();
MasterFileCounter += results.SelectMany(fi => fi).Count();
Now, to write out these files it appears you are trying to aggregate the file names into separate files, but keeping a maximum length (maxSz) of the file.
Here's how to do that:
var aggregateByLength =
results
.SelectMany(fi => fi)
.Aggregate(new [] { new StringBuilder() }.ToList(),
(sbs, s) =>
{
var nl = s + Environment.NewLine;
if (sbs.Last().Length + nl.Length > maxSz)
{
sbs.Add(new StringBuilder(nl));
}
else
{
sbs.Last().Append(nl);
}
return sbs;
});
Writing the files now becomes extremely simple:
foreach (var sb in aggregateByLength)
{
File.WriteAllText(nextOutPutFile(), sb.ToString());
}
So, the full thing becomes:
static void FullDirList(
DirectoryInfo dir, string searchPattern, string excludeFolders, int maxSz, int depth)
{
var results =
from fi in GetFullDirList(dir, searchPattern, depth)
where String.IsNullOrEmpty(excludeFolders)
|| !Regex.IsMatch(fi.FullName, excludeFolders, RegexOptions.IgnoreCase)
group fi.FullName by fi.Directory.FullName;
var directoriesFound = results.Count();
var filesFound = results.SelectMany(fi => fi).Count();
var aggregateByLength =
results
.SelectMany(fi => fi)
.Aggregate(new [] { new StringBuilder() }.ToList(),
(sbs, s) =>
{
var nl = s + Environment.NewLine;
if (sbs.Last().Length + nl.Length > maxSz)
{
sbs.Add(new StringBuilder(nl));
}
else
{
sbs.Last().Append(nl);
}
return sbs;
});
foreach (var sb in aggregateByLength)
{
File.WriteAllText(nextOutPutFile(), sb.ToString());
}
}
I have a problem that is doing my head in.
I want to iterate through the local PC folders, including directories, and calculate a numbering system against each folder in the file system hierarchy.
The root folders should calculate as 1,2,3 etc.
If there were three sub-folders in folder one the calculated numbers should be:
1.1, 1.2,1.3
If there were three sub-folders in the sub-folder above then the calculated numbers should be:
1.1.1, 1.1.2, 1.1.3
If there were three sub-folders in folder two (a root folder) the calculated numbers should be:
2.1, 2.2, 2.3
Or expressed in another way:
1 Root Folder
1.1 Root Sub Folder 1
1.1.1 Sub Folder
1.1.2 Sub Folder
1.2 Root Sub Folder 2
1.2.1 List item
1.2.2 List item
etc. etc.
This logic should then be applied to all folders and sub-folders.
Output Example
1.1.1 | "c:\Root\Folder1\Folder1\"
What I have so far appears to work ok in some situations but can fail in other situations:
private string rootpath = #"C:\FolderHierarchy\";
private string FolderSequenceCountBase = "";
private int CurrentRootPathCount = 0;
private int Counter = 0;
private void CalculateFolderHierarchyNumbers()
{
//Get First List of Folders
string[] Dirs = Directory.GetDirectories(rootpath, "*.*", SearchOption.TopDirectoryOnly);
for (int i = 0; i < Dirs.Count(); i++)
{
FolderSequenceCountBase = (i + 1).ToString();
CurrentRootPathCount = i + 1;
Console.WriteLine("Processed folder '{0}'.", Dirs[i] + " = " + (i + 1));
GetSubDirs(Dirs[i]);
}
}
private void GetSubDirs(string item)
{
//Get next list of folders in the folder hierarchy
string[] SubDirs = Directory.GetDirectories(item, "*.*", SearchOption.TopDirectoryOnly);
foreach (var DirPath in SubDirs)
{
//Increment count of folders within the current folder list
Counter += 1;
Console.WriteLine("Processed folder '{0}'.", DirPath + " = " + FolderSequenceCountBase + "." + Counter);
}
Counter = 0;
//Get next list of folders in the folder hierarchy
foreach (var DirPath in SubDirs)
{
FolderSequenceCountBase += ".1";
GetSubDirs(DirPath);
}
}
Hope this is clear.
Thanks
Rick
So you want to find a file and get it's the number of it in the of directory and all of it's parent directories? Since i found it interesting i've written something from scratch. Note that it's currently not tested but it might give you an idea anyway:
public static IEnumerable<FileEntryInfo> EnumerateFindFiles(string fileToFind, StringComparison comparison = StringComparison.CurrentCultureIgnoreCase, DirectoryInfo rootDir = null, string[] drivesToSearch = null)
{
IEnumerable<FileEntryInfo> foundEntries = Enumerable.Empty<FileEntryInfo>();
if (rootDir != null && drivesToSearch != null)
throw new ArgumentException("Specify either the root-dir or the drives to search, not both");
else if (rootDir != null)
{
foundEntries = EnumerateFindEntryRoot(fileToFind, rootDir, comparison);
}
else
{
if (drivesToSearch == null) // search the entire computer
drivesToSearch = System.Environment.GetLogicalDrives();
foreach (string dr in drivesToSearch)
{
System.IO.DriveInfo di = new System.IO.DriveInfo(dr);
if (!di.IsReady)
{
Console.WriteLine("The drive {0} could not be read", di.Name);
continue;
}
rootDir = di.RootDirectory;
foundEntries = foundEntries.Concat(EnumerateFindEntryRoot(fileToFind, rootDir, comparison));
}
}
foreach (FileEntryInfo entry in foundEntries)
yield return entry;
}
public class FileEntryInfo
{
public FileEntryInfo(string path, int number)
{
this.Path = path;
this.Number = number;
}
public int Number { get; set; }
public string Path { get; set; }
public FileEntryInfo Root { get; set; }
public IEnumerable<int> GetNumberTree()
{
Stack<FileEntryInfo> filo = new Stack<FileEntryInfo>();
FileEntryInfo entry = this;
while (entry.Root != null)
{
filo.Push(entry.Root);
entry = entry.Root;
}
while(filo.Count > 0)
yield return filo.Pop().Number;
yield return this.Number;
}
public override bool Equals(object obj)
{
FileEntryInfo fei = obj as FileEntryInfo;
if(obj == null) return false;
return Number == fei.Number && Path == fei.Path;
}
public override int GetHashCode()
{
return Path.GetHashCode();
}
public override string ToString()
{
return Path;
}
}
private static IEnumerable<FileEntryInfo> EnumerateFindEntryRoot(string fileNameToFind, DirectoryInfo rootDir, StringComparison comparison = StringComparison.CurrentCultureIgnoreCase)
{
Queue<FileEntryInfo> queue = new Queue<FileEntryInfo>();
FileEntryInfo root = new FileEntryInfo(rootDir.FullName, 1);
queue.Enqueue(root);
while (queue.Count > 0)
{
FileEntryInfo fe = queue.Dequeue();
List<FileEntryInfo> validFiles = new List<FileEntryInfo>();
try
{ // you cannot yield from try-catch, hence this approach
FileAttributes attr = File.GetAttributes(fe.Path);
//detect whether its a directory or file
bool isDirectory = (attr & FileAttributes.Directory) == FileAttributes.Directory;
if (isDirectory)
{
int entryCount = 0;
foreach (string entry in Directory.EnumerateFileSystemEntries(fe.Path))
{
entryCount++;
FileEntryInfo subEntry = new FileEntryInfo(entry, entryCount);
subEntry.Root = fe;
queue.Enqueue(subEntry);
attr = File.GetAttributes(entry);
isDirectory = (attr & FileAttributes.Directory) == FileAttributes.Directory;
if(!isDirectory)
validFiles.Add(subEntry);
}
}
} catch (Exception ex)
{
Console.Error.WriteLine(ex); // ignore, proceed
}
foreach (FileEntryInfo entry in validFiles)
{
string fileName = Path.GetFileName(entry.Path);
if (fileName.Equals(fileNameToFind, comparison))
yield return entry;
}
}
}
Here's how i tested it:
var allEntriesFound = EnumerateFindFiles("PresentationFramework.dll").ToList();
if(allEntriesFound.Any())
{
foreach (FileEntryInfo entry in allEntriesFound)
Console.WriteLine("{0}: Number: {1}", entry.Path, entry.Number);
// here your exact requirement:
string result = string.Join(".", allEntriesFound[0].GetNumberTree());
Console.WriteLine(result);
}
Added also a way to specify the root directory to prevent that the whole file system is searched, Usage:
var allEntriesFound = EnumerateFindFiles(
"PresentationFramework.dll",
StringComparison.CurrentCultureIgnoreCase,
new DirectoryInfo(#"C:\Windows"))
.ToList();
I need to get a list of all files on a handheld device whose names fit a certain pattern, such as "ABC.XML"
I adapted code from here (Hernaldo's answer), like so:
public static List<string> GetXMLFiles(string fileType, string dir)
{
string dirName = dir; // call it like so: GetXMLFiles("ABC", "\\"); <= I think the double-whack is what I need for Windows CE device...am I right?
var fileNames = new List<String>();
try
{
foreach (string f in Directory.GetFiles(dirName))
{
if ((f.Contains(fileType)) && (f.Contains(".XML")))
{
fileNames.Add(f);
}
}
foreach (string d in Directory.GetDirectories(dirName))
{
GetXMLFiles(fileType, d);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return fileNames;
}
...but each time the method recursively calls itself (in the GetDirectories() loop), I'm passing the same old first arg. Is it possible (in Compact Framework) to do something like this instead:
public static List<string> GetXMLFiles(optional string fileType, string dir)
{
. . .
foreach (string d in Directory.GetDirectories(dirName))
{
GetXMLFiles(dir = d);
}
. . .
?
UPDATE
According to Habib, this should work (new "try" section):
try
{
string filePattern = string.Format("*{0}*.XML", fileType);
foreach (string f in Directory.GetFiles(dirName, filePattern))
{
fileNames.Add(f);
}
foreach (string d in Directory.GetDirectories(dirName))
{
GetXMLFiles(fileType, d);
}
}
Ja?
UPDATE 2
This goes along with my second response to Alan's comment below:
const string EXTENSION = ".XML";
. . .
try
{
foreach (string f in Directory.GetFiles(dirName))
{
string ext = Path.GetExtension(f);
string fileNameOnly = Path.GetFileNameWithoutExtension(f);
if ((ext.Equals(EXTENSION, StringComparison.Ordinal)) && (fileNameOnly.Contains(fileType)))
{
fileNames.Add(f);
}
}
foreach (string d in Directory.GetDirectories(dirName))
{
GetXMLFiles(fileType, d);
}
}
Optional parameters are a feature of .NET 4.0, so you won't be able to use them. The typical solution to this with a recursive function is to create two overloads for your method with different parameters.
For example,
BinarySearch(array a);
{
BinarySearch(a, -1, array.Length);
}
BinarySearch(array, low, high)
{
//code to update low high
return BinarySearch(array, low, high);
}
In this case, the method which kicks off the recursion has a slightly different signature. You could do the same.
Directory.GetFiles has an overload which takes search pattern you can use that:
Directory.GetFiles(dirName, "ABC.XML")
You can also use wildcards like "*.XML" which would return all XML files.
I am learning c# and need to find a folder when the complete path is unknown. An example would be you know the the album name but not the artist in the music folder. Finding an album name is NOT the final usage for this code but the best example for this question. I am doing this with recursion and limiting the depth of the search. Everything works good except when I find the folder and list the files I want it to stop and return but it does not, it just keeps the recursion going even after I have the the folder. I have also struggled with exception handling like how to skip a folder if permissions are not valid.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace listFoldersTest
{
class Program
{
static void Main(string[] args)
{
Console.SetWindowSize(100, 50);
DirectoryInfo dir = new DirectoryInfo(#"C:\Users\username\Music");
getDirsFiles(dir, 0, 2);
Console.ReadKey();
Console.WriteLine("done");
}
public static void getDirsFiles(DirectoryInfo d, int currentDepth, int maxDepth)
{
String folderToFindName = ("albumName");
bool foundIt = false;
if (currentDepth < maxDepth)
{
DirectoryInfo[] dirs = d.GetDirectories("*.*");
foreach (DirectoryInfo dir in dirs)
{
String pathName = (dir.FullName);
Console.WriteLine("\r{0} ", dir.Name);
if (currentDepth == (maxDepth - 1))
{
if (pathName.IndexOf(folderToFindName) != -1)
{
foundIt = true;
FileInfo[] files = dir.GetFiles("*.*");
foreach (FileInfo file in files)
{
Console.WriteLine("-------------------->> {0} ", file.Name);
} //end foreach files
} // end if pathName
} // end if of get files current depth
if (foundIt == true)
{
return;
}
getDirsFiles(dir, currentDepth + 1, maxDepth);
} //end if foreach directories
} //end if directories current depth
} // end getDirsFiles function
}
}
using System;
using System.IO;
namespace listFoldersTest
{
class Program
{
private static bool foundIt;
static void Main(string[] args)
{
Console.SetWindowSize(100, 50);
try
{
DirectoryInfo dir = new DirectoryInfo(args[0]);
getDirsFiles(dir, 0, 2);
}
catch
{
}
Console.ReadKey();
Console.WriteLine("done");
}
public static void getDirsFiles(DirectoryInfo d, int currentDepth, int maxDepth)
{
if(d == null || foundIt) return;
String folderToFindName = ("albumName");
if (currentDepth < maxDepth)
{
DirectoryInfo[] dirs = d.GetDirectories("*.*");
foreach (DirectoryInfo dir in dirs)
{
String pathName = (dir.FullName);
Console.WriteLine("\r{0} ", dir.Name);
if (currentDepth == (maxDepth - 1))
{
if (pathName.IndexOf(folderToFindName) != -1)
{
foundIt = true;
FileInfo[] files = dir.GetFiles("*.*");
foreach (FileInfo file in files)
{
Console.WriteLine("-------------------->> {0} ", file.Name);
} //end foreach files
return;
} // end if pathName
} // end if of get files current depth
getDirsFiles(dir, currentDepth + 1, maxDepth);
} //end if foreach directories
} //end if directories current depth
} // end getDirsFiles function
}
}
Create a boolean at a global scope.
Default it to false.
When the folder is found, set it to true.
In your recursive function, if the value is true, return and exit from the function without doing anything.
In your case, the foundIt variable is declared and initialized within the function. Declare it at the global level, and check it first thing in the function.
For the exception handling, simply use a try/catch and exit the function if it fails.
You can use the solution from below which doesn't use a global variable.
public static string FindFolder(DirectoryInfo rootDirectory, string folderToFind, int currentDepth, int maxDepth)
{
if(currentDepth == maxDepth)
{
return null;
}
foreach(var directory in rootDirectory.GetDirectories())
{
Console.WriteLine(directory.FullName);
if(directory.Name.Equals(folderToFind,StringComparison.OrdinalIgnoreCase))
{
return directory.FullName;
}
string tempFindResult;
if((tempFindResult = FindFolder(directory,folderToFind,++currentDepth,maxDepth)) != null)
{
return tempFindResult;
}
}
return null;
}
What I think you want to do is break out of the foreach:
if (foundIt == true)
{
break; // instead of return
}
edit
...right, of course, you need to make foundIt a class member. The current scope applied to each iteration of the method.
I have a task to clean up a large number of directories. I want to start at a directory and delete any sub-directories (no matter how deep) that contain no files (files will never be deleted, only directories). The starting directory will then be deleted if it contains no files or subdirectories. I was hoping someone could point me to some existing code for this rather than having to reinvent the wheel. I will be doing this using C#.
Using C# Code.
static void Main(string[] args)
{
processDirectory(#"c:\temp");
}
private static void processDirectory(string startLocation)
{
foreach (var directory in Directory.GetDirectories(startLocation))
{
processDirectory(directory);
if (Directory.GetFiles(directory).Length == 0 &&
Directory.GetDirectories(directory).Length == 0)
{
Directory.Delete(directory, false);
}
}
}
If you can target the .NET 4.0 you can use the new methods on the Directory class to enumerate the directories in order to not pay a performance penalty in listing every file in a directory when you just want to know if there is at least one.
The methods are:
Directory.EnumerateDirectories
Directory.EnumerateFiles
Directory.EnumerateFileSystemEntries
A possible implementation using recursion:
static void Main(string[] args)
{
DeleteEmptyDirs("Start");
}
static void DeleteEmptyDirs(string dir)
{
if (String.IsNullOrEmpty(dir))
throw new ArgumentException(
"Starting directory is a null reference or an empty string",
"dir");
try
{
foreach (var d in Directory.EnumerateDirectories(dir))
{
DeleteEmptyDirs(d);
}
var entries = Directory.EnumerateFileSystemEntries(dir);
if (!entries.Any())
{
try
{
Directory.Delete(dir);
}
catch (UnauthorizedAccessException) { }
catch (DirectoryNotFoundException) { }
}
}
catch (UnauthorizedAccessException) { }
}
You also mention that the directory tree could be very deep so it's possible you might get some exceptions if the path you are probing are too long.
Running the test on C:\Windows 1000 times on the 3 methods mentioned so far yielded this:
GetFiles+GetDirectories:630ms
GetFileSystemEntries:295ms
EnumerateFileSystemEntries.Any:71ms
Running it on an empty folder yielded this (1000 times again):
GetFiles+GetDirectories:131ms
GetFileSystemEntries:66ms
EnumerateFileSystemEntries.Any:64ms
So EnumerateFileSystemEntries is by far the best overall when you are checking for empty folders.
Here's a version that takes advantage of parallel execution to get it done faster in some cases:
public static void DeleteEmptySubdirectories(string parentDirectory){
System.Threading.Tasks.Parallel.ForEach(System.IO.Directory.GetDirectories(parentDirectory), directory => {
DeleteEmptySubdirectories(directory);
if(!System.IO.Directory.EnumerateFileSystemEntries(directory).Any()) System.IO.Directory.Delete(directory, false);
});
}
Here's the same code in single threaded mode:
public static void DeleteEmptySubdirectoriesSingleThread(string parentDirectory){
foreach(string directory in System.IO.Directory.GetDirectories(parentDirectory)){
DeleteEmptySubdirectories(directory);
if(!System.IO.Directory.EnumerateFileSystemEntries(directory).Any()) System.IO.Directory.Delete(directory, false);
}
}
... and here's some sample code you could use to test results in your scenario:
var stopWatch = new System.Diagnostics.Stopwatch();
for(int i = 0; i < 100; i++) {
stopWatch.Restart();
DeleteEmptySubdirectories(rootPath);
stopWatch.Stop();
StatusOutputStream.WriteLine("Parallel: "+stopWatch.ElapsedMilliseconds);
stopWatch.Restart();
DeleteEmptySubdirectoriesSingleThread(rootPath);
stopWatch.Stop();
StatusOutputStream.WriteLine("Single: "+stopWatch.ElapsedMilliseconds);
}
... and here're some results from my machine for a directory that is on a file share across a wide area network. This share currently has only 16 subfolders and 2277 files.
Parallel: 1479
Single: 4724
Parallel: 1691
Single: 5603
Parallel: 1540
Single: 4959
Parallel: 1592
Single: 4792
Parallel: 1671
Single: 4849
Parallel: 1485
Single: 4389
From here, Powershell script to remove empty directories:
$items = Get-ChildItem -Recurse
foreach($item in $items)
{
if( $item.PSIsContainer )
{
$subitems = Get-ChildItem -Recurse -Path $item.FullName
if($subitems -eq $null)
{
"Remove item: " + $item.FullName
Remove-Item $item.FullName
}
$subitems = $null
}
}
Note: use at own risk!
If you rely on DirectoryInfo.Delete only deleting empty directories, you can write a succinct extension method
public static void DeleteEmptyDirs(this DirectoryInfo dir)
{
foreach (DirectoryInfo d in dir.GetDirectories())
d.DeleteEmptyDirs();
try { dir.Delete(); }
catch (IOException) {}
catch (UnauthorizedAccessException) {}
}
Usage:
static void Main()
{
new DirectoryInfo(#"C:\temp").DeleteEmptyDirs();
}
private static void deleteEmptySubFolders(string ffd, bool deleteIfFileSizeZero=false)
{
DirectoryInfo di = new DirectoryInfo(ffd);
foreach (DirectoryInfo diSon in di.GetDirectories("*", SearchOption.TopDirectoryOnly))
{
FileInfo[] fis = diSon.GetFiles("*.*", SearchOption.AllDirectories);
if (fis == null || fis.Length < 1)
{
diSon.Delete(true);
}
else
{
if (deleteIfFileSizeZero)
{
long total = 0;
foreach (FileInfo fi in fis)
{
total = total + fi.Length;
if (total > 0)
{
break;
}
}
if (total == 0)
{
diSon.Delete(true);
continue;
}
}
deleteEmptySubFolders(diSon.FullName, deleteIfFileSizeZero);
}
}
}
//Recursive scan of empty dirs. See example output bottom
string startDir = #"d:\root";
void Scan(string dir, bool stepBack)
{
//directory not empty
if (Directory.GetFileSystemEntries(dir).Length > 0)
{
if (!stepBack)
{
foreach (string subdir in Directory.GetDirectories(dir))
Scan(subdir, false);
}
}
//directory empty so delete it.
else
{
Directory.Delete(dir);
string prevDir = dir.Substring(0, dir.LastIndexOf("\\"));
if (startDir.Length <= prevDir.Length)
Scan(prevDir, true);
}
}
//call like this
Scan(startDir, false);
/*EXAMPLE outputof d:\root with empty subfolders and one filled with files
Scanning d:\root
Scanning d:\root\folder1 (not empty)
Scanning d:\root\folder1\folder1sub1 (not empty)
Scanning d:\root\folder1\folder1sub1\folder2sub2 (deleted!)
Scanning d:\root\folder1\folder1sub1 (deleted!)
Scanning d:\root\folder1 (deleted)
Scanning d:\root (not empty)
Scanning d:\root\folder2 (not empty)
Scanning d:\root\folder2\folder2sub1 (deleted)
Scanning d:\root\folder2 (not empty)
Scanning d:\root\folder2\notempty (not empty) */
foreach (var folder in Directory.GetDirectories(myDir, "*", System.IO.SearchOption.AllDirectories))
{
{
try
{
if (Directory.GetFiles(folder, "*", System.IO.SearchOption.AllDirectories).Length == 0)
Directory.Delete(folder, true);
}
catch { }
}
}