powershell之如何使用 Powershell 管道避免大对象

xing901022 阅读:237 2025-06-02 22:19:02 评论:0

我正在使用自定义函数在 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 时突然停止,并且控制台的错误是“The pipeline已停止”。

我不确定 CSV 文件大小与错误消息有什么关系,但我无法使用任何一种方法处理这个大目录!关于如何让这个过程成功完成的任何建议?

请您参考如下方法:

Get-FolderItem运行 robocopy 列出文件并将其输出转换为 PSObject 数组。这是一个缓慢的操作,严格来说,实际任务不需要这样做。与 foreach 语句 相比,流水线化还增加了很大的开销。在数千或数十万次重复的情况下变得明显。

我们可以超越任何流水线和标准 PowerShell cmdlet 来加快流程,在 10 秒内将 400,000 个文件的信息写入 SSD 驱动器。

  1. .NET Framework 4 或更新版本(自 Win8 起包含,可安装在 Win7/XP 上)IO.DirectoryInfoEnumerateFileSystemInfos以类似管道的非阻塞方式枚举文件;
  2. PowerShell 3 或更新版本,因为它总体上比 PS2 更快;
  3. foreach statement 不需要为每个项目创建 ScriptBlock 上下文因此它比 ForEach cmdlet 快得多
  4. IO.StreamWriter 以非阻塞管道式方式立即写入每个文件的信息;
  5. \\?\ prefix trick取消 260 个字符的路径长度限制;
  6. 手动对目录进行排队以解决“访问被拒绝”错误,否则会停止简单的 IO.DirectoryInfo 枚举;
  7. 进度报告。

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 '^.+?: "(.+?)"$', '$1') 
            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' 


标签:Shell
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

关注我们

一个IT知识分享的公众号