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