用于查找包含数百万文件的文件夹的文件大小和文件计数的PowerShell脚本?
脚本的目的如下:用于查找包含数百万文件的文件夹的文件大小和文件计数的PowerShell脚本?,powershell,sum,Powershell,Sum,脚本的目的如下: 打印在目录中递归找到的文件数 (省略文件夹本身) 打印目录的总文件大小 不会因为大量内存使用而导致计算机崩溃 到目前为止(3)是困难的部分 以下是我到目前为止编写和测试的内容。这在包含100个甚至1000个文件的文件夹上非常有效: $hostname=hostname $directory = "foo" $dteCurrentDate = Get-Date –f "yyyy/MM/dd" $FolderItems = Get-ChildItem $directory -re
$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)很多问题,因为当您流式处理时,每个对象都会在可用时沿管道传递,并且不会占用太多内存,您应该能够处理数百万个文件(尽管这需要时间)
我不相信@Stej的声明,Get ChildItem可能读取目录中的所有条目,然后开始将它们推送到管道中。
是正确的。管道是PowerShell的一个基本概念(提供cmdlet、脚本等。支持它)。它既可以确保处理后的对象在可用时,也可以仅在需要时,沿管道逐个传递。Get ChildItem
的行为不会有所不同
中给出了一个很好的例子
引述:
无论何时,Out Host-Paging命令都是一个有用的管道元素
有很长的输出,您希望缓慢显示。它是
如果操作非常占用CPU,则特别有用。因为
当Out-Host cmdlet具有
已准备好显示的完整页面,在
管道暂停操作,直到下一页输出可用。
如果使用Windows任务管理器监视CPU,则可以看到这一点
以及Windows PowerShell使用的内存
运行以下命令:getchilditem C:\Windows-Recurse
。
将CPU和内存使用情况与以下命令进行比较:Get ChildItem
C:\Windows-Recurse | Out主机-分页
在c:\
上使用getchilditem
作为基准(大约179516个文件,不是百万,但足够好):
运行$a=gci c:\-recurse
(然后执行$a.count
)后的内存使用量为527332k
运行gci c:\-recurse | measure object
后的内存使用量为59452K
,并且在80000K左右从未超过
(内存-专用工作集-来自TaskManager,查看powershell.exe
进程的内存。最初,它大约是22000k
)
我还尝试了200万个文件(创建它们花了我一段时间!)
类似实验:
运行$a=gci c:\-recurse
(然后执行$a.count
)后的内存使用量为2808508k
运行gci c:\-recurse | measure object
时的内存使用量为308060k
,从未超过400000k
。完成后,它必须执行[GC]::Collect()
才能返回到22000k
级别
我仍然相信Get ChildItem
和管道技术可以为数百万个文件带来巨大的内存改进。Get ChildItem
可能读取目录中的所有条目,然后开始将它们推送到管道。如果Get ChildItem
工作不正常,请尝试切换到.NET 4.0和使用枚举文件
和枚举目录
:
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
这里最昂贵的部分是带有新对象io.FileInfo$f
的部分,因为EnumerateFiles
只返回文件名。因此,如果只计算文件数就足够了,您可以对该行进行注释
请参阅堆栈溢出问题
学习如何使用.NET4.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)。请自己检查。以下函数非常酷,可以快速计算文件夹的大小,但并不总是有效(尤其是当存在权限问题或文件夹路径过长时)
一个很好的解决方案是小心地将该线程更改为.Net-您是标准的\v2 PowerShell无法与所有功能(包括远程处理)一起使用。最好复制文件夹并在那里进行更改,只有在需要使用v4时才调用此PS。它们并行工作。@Matt,我在.NET 4.0下运行PowerShell几个月了,没有问题。(更改了配置文件)远程处理工作正常?这很酷。大约3-4个月前,我遇到了一些问题-我想知道我是否使用了最新版本的.net v4。我在Win7Win server 2008和它工作期间不时使用远程处理:)再试一次,我希望它会正常。@stej-作为一名C#dev,我同意你的看法,这就是我在C#中的做法。但我想看看Get childItem是否真的能完成这项工作。你能试试我在更新后的答案中所做的命令并测量内存使用情况吗?Get ChildItem
实际上表现不同。@你可能想添加-force
标志来获取ChildItem,这样它就可以读取系统和隐藏文件(我讨厌这个“功能”)。@stej-I比较了内存使用情况。你能从我更新的答案中尝试同样的方法吗?@man
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
...
Function sizeFolder($path) # Return the size in MB.
{
$objFSO = New-Object -com Scripting.FileSystemObject
("{0:N2}" -f (($objFSO.GetFolder($path).Size) / 1MB))
}