uso - C#Eliminar todos los subdirectorios vacíos
eliminar una carpeta desde c# (8)
Tengo una tarea para limpiar una gran cantidad de directorios. Quiero comenzar en un directorio y eliminar cualquier subdirectorio (sin importar la profundidad) que no contenga archivos (los archivos nunca serán eliminados, solo los directorios). El directorio inicial se eliminará si no contiene archivos o subdirectorios. Esperaba que alguien me indicara algún código existente para esto en lugar de tener que reinventar la rueda. Haré esto usando C #.
Aquí hay una versión que aprovecha la ejecución paralela para hacerlo más rápido en algunos casos:
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);
});
}
Este es el mismo código en modo de un solo hilo:
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);
}
}
... y aquí hay un código de muestra que podría usar para evaluar los resultados en su escenario:
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);
}
... y aquí hay algunos resultados de mi máquina para un directorio compartido en una red de área amplia. Actualmente, este recurso compartido solo tiene 16 subcarpetas y 2277 archivos.
Parallel: 1479
Single: 4724
Parallel: 1691
Single: 5603
Parallel: 1540
Single: 4959
Parallel: 1592
Single: 4792
Parallel: 1671
Single: 4849
Parallel: 1485
Single: 4389
Desde aquí, secuencia de comandos Powershell para eliminar directorios vacíos :
$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
}
}
Nota : ¡use bajo su propio riesgo!
Ejecutar la prueba en C: / Windows 1000 veces en los 3 métodos mencionados hasta ahora produjo esto:
GetFiles+GetDirectories:630ms
GetFileSystemEntries:295ms
EnumerateFileSystemEntries.Any:71ms
Ejecutarlo en una carpeta vacía arrojó esto (1000 veces más):
GetFiles+GetDirectories:131ms
GetFileSystemEntries:66ms
EnumerateFileSystemEntries.Any:64ms
Así que EnumerateFileSystemEntries es de lejos el mejor en general cuando se buscan carpetas vacías.
Si confía en DirectoryInfo.Delete
solo eliminando directorios vacíos, puede escribir un método de extensión sucinto
public static void DeleteEmptyDirs(this DirectoryInfo dir)
{
foreach (DirectoryInfo d in dir.GetDirectories())
d.DeleteEmptyDirs();
try { dir.Delete(); }
catch (IOException) {}
catch (UnauthorizedAccessException) {}
}
Uso:
static void Main()
{
new DirectoryInfo(@"C:/temp").DeleteEmptyDirs();
}
Si puede orientar el .NET 4.0, puede usar los nuevos métodos en la clase del Directory
para enumerar los directorios para no pagar una penalización de rendimiento al listar cada archivo en un directorio cuando solo desea saber si hay al menos uno.
Los métodos son:
-
Directory.EnumerateDirectories
-
Directory.EnumerateFiles
-
Directory.EnumerateFileSystemEntries
Una posible implementación usando recursión:
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) { }
}
También mencionas que el árbol de directorios podría ser muy profundo, por lo que es posible que obtengas algunas excepciones si la ruta que estás explorando es demasiado larga.
Usando 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);
}
}
}
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) */