如何使用 Powershell 管道避免大对象?
How to use Powershell Pipeline to Avoid Large Objects?
我正在使用自定义函数在 8TB 驱动器(数千个文件)上执行 DIR 命令(递归文件列表)。
我的第一次迭代是:
$results = $PATHS | % {Get-FolderItem -Path "$($_)" } | Select Name,DirectoryName,Length,LastWriteTime
$results | Export-CVS -Path $csvfile -Force -Encoding UTF8 -NoTypeInformation -Delimiter "|"
这导致了一个巨大的 $results 变量,并通过使 powershell 进程尖峰使用 99%-100% CPU 来减慢系统的爬行速度随着处理的进行。
我决定使用管道的力量直接写入 CSV 文件(大概是释放内存)而不是保存到中间变量,并想出了这个:
$PATHS | % {Get-FolderItem -Path "$($_)" } | Select Name,DirectoryName,Length,LastWriteTime | ConvertTo-CSV -NoTypeInformation -Delimiter "|" | Out-File -FilePath $csvfile -Force -Encoding UTF8
这似乎工作正常(CSV 文件正在增长..并且 CPU 似乎很稳定)但是当 CSV 文件大小达到 ~200MB 时突然停止,并且控制台的错误是“管道已停止”。
我不确定 CSV 文件大小与错误消息有什么关系,但我无法用任何一种方法处理这个大目录!关于如何成功完成此过程的任何建议?
Get-FolderItem 运行 robocopy
列出文件并将其输出转换为 PSObject 数组。这是一个缓慢的操作,严格来说,实际任务不需要这样做。与 foreach 语句 相比,流水线还增加了很大的开销。在数千或数十万次重复的情况下变得明显。
我们可以通过任何流水线和标准的 PowerShell cmdlet 来加快流程,在 10 秒内将 400,000 个文件的信息写入 SSD 驱动器。
- .NET Framework 4 或更新版本(自 Win8 起包含,可在 Win7/XP 上安装)
IO.DirectoryInfo
的 EnumerateFileSystemInfos 以非阻塞管道式方式枚举文件;
- PowerShell 3 或更新版本,因为它比 PS2 总体上更快;
foreach
statement 不需要为每个项目创建 ScriptBlock 上下文因此它比 ForEach
cmdlet 快得多
IO.StreamWriter
以非阻塞管道式方式立即写入每个文件的信息;
\?\
prefix trick 取消 260 个字符的路径长度限制;
- 手动处理目录排队以克服 "access denied" 错误,否则会停止天真的 IO.DirectoryInfo 枚举;
- 进度报告。
function List-PathsInCsv([string[]]$PATHS, [string]$destination) {
$prefix = '\?\' #' UNC prefix lifts 260 character path length restriction
$writer = [IO.StreamWriter]::new($destination, $false, [Text.Encoding]::UTF8, 1MB)
$writer.WriteLine('Name|Directory|Length|LastWriteTime')
$queue = [Collections.Generic.Queue[string]]($PATHS -replace '^', $prefix)
$numFiles = 0
while ($queue.Count) {
$dirInfo = [IO.DirectoryInfo]$queue.Dequeue()
try {
$dirEnumerator = $dirInfo.EnumerateFileSystemInfos()
} catch {
Write-Warning ("$_".replace($prefix, '') -replace '^.+?: "(.+?)"$', '')
continue
}
$dirName = $dirInfo.FullName.replace($prefix, '')
foreach ($entry in $dirEnumerator) {
if ($entry -is [IO.FileInfo]) {
$writer.WriteLine([string]::Join('|', @(
$entry.Name
$dirName
$entry.Length
$entry.LastWriteTime
)))
} else {
$queue.Enqueue($entry.FullName)
}
if (++$numFiles % 1000 -eq 0) {
Write-Progress -activity Digging -status "$numFiles files, $dirName"
}
}
}
$writer.Close()
Write-Progress -activity Digging -Completed
}
用法:
List-PathsInCsv 'c:\windows', 'd:\foo\bar' 'r:\output.csv'
不要使用 robocopy,使用本机 PowerShell 命令,如下所示:
$PATHS = 'c:\temp', 'c:\temp2'
$csvfile='c:\temp\listresult.csv'
$PATHS | % {Get-ChildItem $_ -file -recurse } | Select Name,DirectoryName,Length,LastWriteTime | export-csv $csvfile -Delimiter '|' -Encoding UTF8 -NoType
非纯粹主义者的简短版本:
$PATHS | % {gci $_ -file -rec } | Select Name,DirectoryName,Length,LastWriteTime | epcsv $csvfile -D '|' -E UTF8 -NoT
我正在使用自定义函数在 8TB 驱动器(数千个文件)上执行 DIR 命令(递归文件列表)。
我的第一次迭代是:
$results = $PATHS | % {Get-FolderItem -Path "$($_)" } | Select Name,DirectoryName,Length,LastWriteTime
$results | Export-CVS -Path $csvfile -Force -Encoding UTF8 -NoTypeInformation -Delimiter "|"
这导致了一个巨大的 $results 变量,并通过使 powershell 进程尖峰使用 99%-100% CPU 来减慢系统的爬行速度随着处理的进行。
我决定使用管道的力量直接写入 CSV 文件(大概是释放内存)而不是保存到中间变量,并想出了这个:
$PATHS | % {Get-FolderItem -Path "$($_)" } | Select Name,DirectoryName,Length,LastWriteTime | ConvertTo-CSV -NoTypeInformation -Delimiter "|" | Out-File -FilePath $csvfile -Force -Encoding UTF8
这似乎工作正常(CSV 文件正在增长..并且 CPU 似乎很稳定)但是当 CSV 文件大小达到 ~200MB 时突然停止,并且控制台的错误是“管道已停止”。
我不确定 CSV 文件大小与错误消息有什么关系,但我无法用任何一种方法处理这个大目录!关于如何成功完成此过程的任何建议?
Get-FolderItem 运行 robocopy
列出文件并将其输出转换为 PSObject 数组。这是一个缓慢的操作,严格来说,实际任务不需要这样做。与 foreach 语句 相比,流水线还增加了很大的开销。在数千或数十万次重复的情况下变得明显。
我们可以通过任何流水线和标准的 PowerShell cmdlet 来加快流程,在 10 秒内将 400,000 个文件的信息写入 SSD 驱动器。
- .NET Framework 4 或更新版本(自 Win8 起包含,可在 Win7/XP 上安装)
IO.DirectoryInfo
的 EnumerateFileSystemInfos 以非阻塞管道式方式枚举文件; - PowerShell 3 或更新版本,因为它比 PS2 总体上更快;
foreach
statement 不需要为每个项目创建 ScriptBlock 上下文因此它比ForEach
cmdlet 快得多
IO.StreamWriter
以非阻塞管道式方式立即写入每个文件的信息;\?\
prefix trick 取消 260 个字符的路径长度限制;- 手动处理目录排队以克服 "access denied" 错误,否则会停止天真的 IO.DirectoryInfo 枚举;
- 进度报告。
function List-PathsInCsv([string[]]$PATHS, [string]$destination) {
$prefix = '\?\' #' UNC prefix lifts 260 character path length restriction
$writer = [IO.StreamWriter]::new($destination, $false, [Text.Encoding]::UTF8, 1MB)
$writer.WriteLine('Name|Directory|Length|LastWriteTime')
$queue = [Collections.Generic.Queue[string]]($PATHS -replace '^', $prefix)
$numFiles = 0
while ($queue.Count) {
$dirInfo = [IO.DirectoryInfo]$queue.Dequeue()
try {
$dirEnumerator = $dirInfo.EnumerateFileSystemInfos()
} catch {
Write-Warning ("$_".replace($prefix, '') -replace '^.+?: "(.+?)"$', '')
continue
}
$dirName = $dirInfo.FullName.replace($prefix, '')
foreach ($entry in $dirEnumerator) {
if ($entry -is [IO.FileInfo]) {
$writer.WriteLine([string]::Join('|', @(
$entry.Name
$dirName
$entry.Length
$entry.LastWriteTime
)))
} else {
$queue.Enqueue($entry.FullName)
}
if (++$numFiles % 1000 -eq 0) {
Write-Progress -activity Digging -status "$numFiles files, $dirName"
}
}
}
$writer.Close()
Write-Progress -activity Digging -Completed
}
用法:
List-PathsInCsv 'c:\windows', 'd:\foo\bar' 'r:\output.csv'
不要使用 robocopy,使用本机 PowerShell 命令,如下所示:
$PATHS = 'c:\temp', 'c:\temp2'
$csvfile='c:\temp\listresult.csv'
$PATHS | % {Get-ChildItem $_ -file -recurse } | Select Name,DirectoryName,Length,LastWriteTime | export-csv $csvfile -Delimiter '|' -Encoding UTF8 -NoType
非纯粹主义者的简短版本:
$PATHS | % {gci $_ -file -rec } | Select Name,DirectoryName,Length,LastWriteTime | epcsv $csvfile -D '|' -E UTF8 -NoT