J'ai pour tâche de nettoyer un grand nombre de répertoires. Je veux commencer à un répertoire et supprimer tous les sous-répertoires (peu importe la profondeur) qui ne contiennent aucun fichier (les fichiers ne seront jamais supprimés, seulement les répertoires). Le répertoire de départ sera alors supprimé s'il ne contient aucun fichier ou sous-répertoire. J'espérais que quelqu'un pourrait me diriger vers un code existant plutôt que de réinventer la roue. Je vais le faire en utilisant C#.C# Supprimer tous les sous-répertoires vides
Répondre
Utilisation du code C#.
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);
}
}
}
A partir de là, 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
}
}
Remarque: utilisation à vos propres risques!
Powershell n'est pas une option, désolé. Merci quand même. – Jay
Si vous pouvez cibler le .NET 4.0, vous pouvez utiliser les nouvelles méthodes de la classe Directory
d'énumérer les répertoires afin de ne pas payer une pénalité de performance dans la liste tous les fichiers dans un répertoire où vous voulez juste savoir s'il y en a au moins un.
Les méthodes sont les suivantes:
Directory.EnumerateDirectories
Directory.EnumerateFiles
Directory.EnumerateFileSystemEntries
Une implémentation possible en utilisant récursion:
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) { }
}
Vous mentionnez également que l'arborescence de répertoires peut être très profonde, il est donc possible que vous obteniez des exceptions si le chemin que vous recherchez est trop long.
Merci, mais malheureusement, nous n'utilisons pas .Net 4.0 .Je souhaite que nous pourrions comme j'ai environ 20.000 dossiers t o processus. – Jay
Bonne réponse. Au lieu de 'if (String.IsNullOrEmpty (entries.FirstOrDefault()))', vous pouvez également utiliser 'if (! Entries.Any())', ce qui est un peu plus propre à mon humble avis. –
@Danko Durbić, tout à fait d'accord avec vous, je n'ai pas remarqué la surcharge sans paramètres et me demandais déjà pourquoi 'Enumerable' n'avait pas quelque chose à vérifier rapidement pour un' IEnumerable' vide. Merci, j'ai mis à jour la réponse. –
Si vous comptez sur DirectoryInfo.Delete
que la suppression des répertoires vides, vous pouvez écrire une méthode d'extension succincte
public static void DeleteEmptyDirs(this DirectoryInfo dir)
{
foreach (DirectoryInfo d in dir.GetDirectories())
d.DeleteEmptyDirs();
try { dir.Delete(); }
catch (IOException) {}
catch (UnauthorizedAccessException) {}
}
Utilisation:
static void Main()
{
new DirectoryInfo(@"C:\temp").DeleteEmptyDirs();
}
//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) */
Cet algoritm supprimera tous les répertoires vides dans un répertoire de démarrage donné. Lorsqu'un répertoire est supprimé, le répertoire précédent sera ré-analysé car il est possible que le répertoire précédent soit maintenant vide. – Jamil
Ce n'est pas nécessaire. Vous devriez penser aux avantages de la récursivité et peut-être déboguer lentement à travers les réponses déjà fournies. – Oliver
Vrai, mais qu'est-ce qui n'est pas récursif à propos de ça? J'étais un peu trop rapide et j'ai donc fait une petite correction. Voir l'exemple de sortie sur ce que fait ce code. Je pense que c'est efficace. – Jamil
Exécution du test sur C: \ Windows 1000 fois sur les 3 méthodes mentionnées jusqu'à présent cédé ceci:
GetFiles+GetDirectories:630ms
GetFileSystemEntries:295ms
EnumerateFileSystemEntries.Any:71ms
Courir sur un dossier vide a abouti à ce (1000 fois): encore
GetFiles+GetDirectories:131ms
GetFileSystemEntries:66ms
EnumerateFileSystemEntries.Any:64ms
Alors EnumerateFileSystemEntries est de loin le meilleur ensemble lorsque vous vérifiez les dossiers vides.
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);
}
}
}
est ici une version qui profite de l'exécution en parallèle pour le faire plus rapidement dans certains cas:
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);
});
}
est ici le même code en mode mono-thread:
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);
}
}
... et voici quelques exemples de code que vous pourriez utiliser pour tester les résultats dans votre scénario:
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);
}
... et voici quelques résultats de ma machine pour un répertoire qui se trouve sur un partage de fichiers sur un réseau étendu. Ce partage a actuellement seulement 16 sous-dossiers et 2277 fichiers.
Parallel: 1479
Single: 4724
Parallel: 1691
Single: 5603
Parallel: 1540
Single: 4959
Parallel: 1592
Single: 4792
Parallel: 1671
Single: 4849
Parallel: 1485
Single: 4389
Très utile! merci, Bill – BillW
Pourquoi utiliser powershell ...? –