web-dev-qa-db-fra.com

Un script PowerShell permettant de trouver la taille et le nombre de fichiers d’un dossier contenant des millions de fichiers?

Le but du script est le suivant:

  1. Imprimer le nombre de fichiers trouvés récursivement dans un répertoire (En omettant les dossiers eux-mêmes)
  2. Imprimer la taille totale du fichier du répertoire
  3. Pas crash l'ordinateur en raison de l'utilisation massive de la mémoire.

Jusqu'ici (3) est la partie difficile.

Voici ce que j'ai écrit et testé jusqu'à présent. Cela fonctionne parfaitement sur des dossiers contenant une centaine, voire mille fichiers:

$hostname=hostname
$directory = "foo"
$dteCurrentDate = Get-Date –f "yyyy/MM/dd"

$FolderItems = Get-ChildItem $directory -recurse
$Measurement = $FolderItems | Measure-Object -property length -sum
$colitems = $FolderItems | measure-Object -property length -sum
"$hostname;{0:N2}" -f ($colitems.sum / 1MB) + "MB;" + $Measurement.count + " files;" + "$dteCurrentDate"

Sur les dossiers contenant des millions de fichiers, cependant, la variable $colitems devient tellement massive à partir de la collecte d'informations de millions de fichiers qu'elle rend le système instable. Existe-t-il un moyen plus efficace de dessiner et de stocker cette information?

19
Stephen Wood

Si vous utilisez la diffusion en continu et le traitement en pipeline, vous devriez réduire beaucoup le problème avec (3), car lorsque vous diffusez en continu, chaque objet est transmis le long du pipeline au fur et à mesure de leur disponibilité et ne nécessite pas beaucoup de mémoire. traiter des millions de fichiers (bien que cela prenne du temps).

Get-ChildItem $directory -recurse | Measure-Object -property length -sum

Je ne crois pas que la déclaration de @ Stej, Get-ChildItem probably reads all entries in the directory and then begins pushing them to the pipeline., soit vraie. Le traitement en pipeline est un concept fondamental de PowerShell (fournissez-le avec les applets de commande, scripts, etc.). Cela garantit à la fois que les objets traités passent dans le pipeline un par un au fur et à mesure de leur disponibilité et que uniquement lorsqu'ils sont nécessaires. Get-ChildItem ne va pas se comporter différemment.

Vous en trouverez un bon exemple dans Présentation du pipeline Windows PowerShell.

Citant de cela:

La commande Out-Host -Paging est un élément de pipeline utile chaque fois que vous avoir une longue sortie que vous voudriez afficher lentement. Il est particulièrement utile si l'opération nécessite beaucoup de ressources processeur. Parce que le traitement est transféré à la cmdlet Out-Host quand elle a un page complète prête à être affichée, les applets de commande qui la précèdent dans le opération d’arrêt du pipeline jusqu’à ce que la page de sortie suivante soit disponible . Vous pouvez le voir si vous utilisez le gestionnaire de tâches Windows pour surveiller le processeur et utilisation de la mémoire par Windows PowerShell.

Exécutez la commande suivante: Get-ChildItem C:\Windows -Recurse. Comparez l'utilisation du processeur et de la mémoire à cette commande: Get-ChildItem C:\Windows -Recurse | Out-Host -Paging.

Benchmark sur l'utilisation de Get-ChildItem sur c:\ (environ 179516 fichiers, pas des millions, mais suffisamment bon):

L'utilisation de la mémoire après l'exécution de $a = gci c:\ -recurse (puis de $a.count) était de 527,332K.

L'utilisation de la mémoire après l'exécution de gci c:\ -recurse | measure-object était 59,452K et n'a jamais dépassé 80,000K.

(Mémoire - Ensemble de travail privé - à partir de TaskManager, voir la mémoire pour le processus powershell.exe. Au départ, il s'agissait de 22,000K.)

J'ai aussi essayé avec deux millions de fichiers (cela m'a pris du temps pour les créer!)

Expérience similaire:

L'utilisation de la mémoire après l'exécution de $a = gci c:\ -recurse (puis de $a.count) était de 2,808,508K.

L'utilisation de la mémoire lors de l'exécution de gci c:\ -recurse | measure-object était 308,060K et n'a jamais dépassé 400,000K. Une fois terminé, il a dû effectuer une [GC]::Collect() pour pouvoir revenir aux niveaux 22,000K.

Je suis toujours convaincu que Get-ChildItem et le traitement en pipeline peuvent vous apporter d’importantes améliorations de la mémoire, même pour des millions de fichiers.

31
manojlds

Get-ChildItem lit probablement toutes les entrées du répertoire, puis commence à les envoyer au pipeline. Si Get-ChildItem ne fonctionne pas bien, essayez de passer à .NET 4.0 et utilisez EnumerateFiles et EnumeratedDirectories:

function Get-HugeDirStats($directory) {
    function go($dir, $stats)
    {
        foreach ($f in [system.io.Directory]::EnumerateFiles($dir))
        {
            $stats.Count++
            $stats.Size += (New-Object io.FileInfo $f).Length
        }
        foreach ($d in [system.io.directory]::EnumerateDirectories($dir))
        {
            go $d $stats
        }
    }
    $statistics = New-Object PsObject -Property @{Count = 0; Size = [long]0 }
    go $directory $statistics

    $statistics
}

#example
$stats = Get-HugeDirStats c:\windows

Ici, la partie la plus chère est celle avec New-Object io.FileInfo $f, car EnumerateFiles renvoie uniquement les noms de fichiers. Donc, si seul le nombre de fichiers est suffisant, vous pouvez commenter la ligne.

Voir la question relative au débordement de pile Comment exécuter PowerShell avec le runtime .NET 4? Pour apprendre à utiliser .NET 4.0.


Vous pouvez également utiliser de vieilles méthodes simples et rapides, mais lire tous les fichiers du répertoire. Cela dépend donc de vos besoins, essayez-le. Plus tard, il y a une comparaison de toutes les méthodes.

function Get-HugeDirStats2($directory) {
    function go($dir, $stats)
    {
        foreach ($f in $dir.GetFiles())
        {
            $stats.Count++
            $stats.Size += $f.Length
        }
        foreach ($d in $dir.GetDirectories())
        {
            go $d $stats
        }
    }
    $statistics = New-Object PsObject -Property @{Count = 0; Size = [long]0 }
    go (new-object IO.DirectoryInfo $directory) $statistics

    $statistics
}

Comparaison:

Measure-Command { $stats = Get-HugeDirStats c:\windows }
Measure-Command { $stats = Get-HugeDirStats2 c:\windows }
Measure-Command { Get-ChildItem c:\windows -recurse | Measure-Object -property length -sum }
TotalSeconds      : 64,2217378
...

TotalSeconds      : 12,5851008
...

TotalSeconds      : 20,4329362
...

@manojlds: Le pipeline est un concept fondamental. Mais en tant que concept, cela n’a rien à voir avec les fournisseurs. Le fournisseur de système de fichiers s'appuie sur l'implémentation .NET (.NET 2.0) qui ne dispose pas de capacités d'évaluation paresseuses (~ énumérateurs). Vérifiez cela vous-même.

8
stej

La fonction suivante est assez cool et permet de calculer rapidement la taille d'un dossier, mais cela ne fonctionne pas toujours (surtout en cas de problème d'autorisation ou de chemin d'accès trop long au dossier).

Function sizeFolder($path) # Return the size in MB.
{
    $objFSO = New-Object -com  Scripting.FileSystemObject
    ("{0:N2}" -f (($objFSO.GetFolder($path).Size) / 1MB))
}
0
fretman92