Подтвердить что ты не робот

PowerShell script, чтобы найти размер файла и количество файлов в папке с миллионами файлов?

Цель script заключается в следующем:

  • Распечатайте количество рекурсивно найденных файлов в каталоге (опуская сами папки)
  • Распечатайте общий размер файла сумм в каталоге
  • Не сбой компьютера из-за огромного использования памяти.

До сих пор (3) является трудной частью.

Вот что я написал и протестировал до сих пор. Это отлично работает в папках со сто или даже тысячей файлов:

$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"

В папках с миллионами файлов переменная $colitems становится настолько массивной из коллекции информации миллионов файлов, что делает ее неустойчивой. Есть ли более эффективный способ рисовать и хранить эту информацию?

4b9b3361

Ответ 1

Если вы используете потоковое вещание и конвейерную обработку, вам следует уменьшить проблему с (3) много, потому что когда вы передаете поток, каждый объект передается по конвейеру по мере их поступления и не занимает много памяти, и вы должны иметь возможность обрабатывать миллионы файлов (хотя потребуется время).

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

Я не верю, что утверждение @Stej, Get-ChildItem probably reads all entries in the directory and then begins pushing them to the pipeline., истинно. Конвейеризация - это фундаментальная концепция PowerShell (предоставить командлеты, скрипты и т.д.). Это гарантирует, что обработанные объекты передаются по конвейеру один за другим, когда и когда они доступны, а также только тогда, когда они необходимы. Get-ChildItem не будет вести себя иначе.

Отличный пример этого приведен в Понимание Pipeline Windows PowerShell.

Цитата из этого:

Команда Out-Host -Paging - полезный элемент конвейера, когда вы имеют длительный вывод, который вы хотели бы отображать медленно. это особенно полезно, если операция очень интенсивна для процессора. Потому как обработка передается командлету Out-Host, когда он имеет полная страница, готовая для отображения, командлеты, которые предшествуют ей в остановка трубопровода, пока не появится следующая страница выхода. Это можно увидеть, если вы используете диспетчер задач Windows для мониторинга процессора и использование памяти Windows PowerShell.

Выполните следующую команду: Get-ChildItem C:\Windows -Recurse. Сравните использование процессора и памяти с этой командой: Get-ChildItem C:\Windows -Recurse | Out-Host -Paging.

Тест на использование Get-ChildItem на c:\ (около 179516 файлов, а не миллионы, но достаточно хорошо):

Использование памяти после запуска $a = gci c:\ -recurse (а затем выполнение $a.count) было 527,332K.

Использование памяти после запуска gci c:\ -recurse | measure-object было 59,452K и никогда не было выше 80,000K.

(Память - Частный рабочий набор - из TaskManager, видя память для процесса powershell.exe. Первоначально это было около 22,000K.)

Я также пробовал с двумя миллионами файлов (мне понадобилось некоторое время для их создания!)

Аналогичный эксперимент:

Использование памяти после запуска $a = gci c:\ -recurse (а затем выполнение $a.count) было 2,808,508K.

Использование памяти во время работы gci c:\ -recurse | measure-object было 308,060K и никогда не превышало значение 400,000K. После этого он должен был сделать [GC]::Collect(), чтобы вернуться к уровням 22,000K.

Я по-прежнему убежден, что Get-ChildItem и конвейерная обработка могут дать вам большие улучшения в памяти даже для миллионов файлов.

Ответ 2

Get-ChildItem, вероятно, читает все записи в каталоге, а затем начинает толкать их в конвейер. В случае, если Get-ChildItem не работает, попробуйте переключиться на .NET 4.0 и используйте EnumerateFiles и 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

Здесь самая дорогая часть - с New-Object io.FileInfo $f, потому что EnumerateFiles возвращает только имена файлов. Поэтому, если достаточно всего количества файлов, вы можете прокомментировать строку.

См. вопрос о переполнении стека Как запустить PowerShell с помощью среды выполнения .NET 4? чтобы узнать, как использовать .NET 4.0.


Вы также можете использовать простые старые методы, которые также бывают быстрыми, но читайте все файлы в каталоге. Так что это зависит от ваших потребностей, просто попробуйте. Позже существует сравнение всех методов.

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
}

Сравнение:

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: Конвейеризация - фундаментальная концепция. Но в качестве концепции это не имеет никакого отношения к провайдерам. Поставщик файловой системы полагается на реализацию .NET(.NET 2.0), которая не имеет ленивых возможностей оценки (~ перечисления). Убедитесь, что вы сами.

Ответ 3

Следующая функция довольно крутая и быстро вычисляет размер папки, но она не всегда работает (особенно когда есть проблема с разрешением или слишком длинный путь к папке).

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