Сценарий PowerShell для определения размера файла и количества файлов в папке с миллионами файлов? - PullRequest
21 голосов
/ 16 августа 2011

Назначение скрипта следующее:

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

Пока (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 становится настолько большой из-за сбора информации о миллионах файлов, что делает систему нестабильной. Есть ли более эффективный способ получения и хранения этой информации?

Ответы [ 3 ]

33 голосов
/ 16 августа 2011

Если вы используете потоковую передачу и конвейерную обработку, вы должны значительно уменьшить проблему с (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 не будет вести себя иначе.

Прекрасный пример этого дан в Понимание конвейера Windows PowerShell .

Цитирование изit:

Команда 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 и конвейеризация могут дать вам большие улучшения памяти даже для миллионов файлов.

9 голосов
/ 16 августа 2011

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), которая не имеет возможности отложенной оценки (~ перечислители). Проверьте это сами.

0 голосов
/ 10 сентября 2012

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

Function sizeFolder($path) # Return the size in MB.
{
    $objFSO = New-Object -com  Scripting.FileSystemObject
    ("{0:N2}" -f (($objFSO.GetFolder($path).Size) / 1MB))
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...