【问题标题】:powershell slow(?) - write names of subfolders to a text filepowershell slow(?) - 将子文件夹的名称写入文本文件
【发布时间】:2014-12-23 16:56:09
【问题描述】:

我的 Powershell 脚本似乎很慢,当我在 ISE 中运行以下代码时,它一直在运行,不会停止。

我正在尝试将文件夹中的子文件夹列表(文件夹路径在 $scratchpart 中)写入文本文件。有 >30k 子文件夹

$limit = (Get-Date).AddDays(-15)
$path = "E:\Data\PathToScratch.txt"
$scratchpath = Get-Content $path -TotalCount 1

Get-ChildItem -Path $scratchpath -Recurse -Force | Where-Object { $_.PSIsContainer -and $_.CreationTime -lt $limit } | Add-Content C:\Data\eProposal\POC\ScratchContents.txt

如果我的方法不是最优的,请告诉我。最后,我会阅读文本文件,压缩子文件夹以进行归档并删除它们。

提前感谢您的帮助。我是 PS 新手,在 MVA 上看过一些视频

【问题讨论】:

  • 我想我会为所有*文件夹(以给定最大值取模)使用后台作业 (Start-Job) 并结合结果。

标签: powershell


【解决方案1】:

Add-ContentSet-Content 甚至 Out-File 在 PowerShell 中的速度非常慢。这是因为每次调用都会打开文件、写入文件并关闭句柄。它没有比这更聪明的事情了。

在您考虑管道如何与Get-ChildItem(以及Where-ObjectSelect-Object)一起工作之前,这听起来还不错。它不会等到完成后才开始将对象传递到管道中。一旦提供者返回对象,它就会开始传递对象。对于大型结果集,这意味着对象在几个完成处理后很长时间仍在管道中馈送。一般来说,这很棒!这意味着系统将更有效地运行,这就是为什么这样的东西:

$x = Get-ChildItem;
$x | ForEach-Object { [...] };

比这样的东西慢得多:

Get-ChildItem | ForEach-Object { [...] };

这就是为什么像这样的东西似乎停滞不前的原因:

Get-ChildItem | Sort-Object Name | ForEach-Object { [...] };

Sort-Object cmdlet 需要等待,直到它收到所有管道对象后才能进行排序。它必须能够排序。排序本身几乎是瞬时的。它只是等待获得完整结果的 cmdlet。

Add-Content 的问题在于,它对管道的体验不是“这是一个要写一次的大字符串”,而是“这是一个要写的字符串。这是一个要写的字符串。这是一个要写的字符串。这是要写的字符串。”您将在此处逐行向Add-Content 发送内容。 每一行都会实例化一个对Add-Content的新调用,要求打开、写入和关闭文件。如果将Get-ChildItem [...] | Where-Object [...] 的结果分配给一个变量,然后将整个变量一次写入文件,您可能会看到更好的性能:

$limit = (Get-Date).AddDays(-15);
$path = "E:\Data\PathToScratch.txt";
$scratchpath = Get-Content $path -TotalCount 1;

$Results = Get-ChildItem -Path $scratchpath -Recurse -Force -Directory | `
    Where-Object{$_.CreationTime -lt $limit } | `
    Select-Object -ExpandPropery FullName;

Add-Content C:\Data\eProposal\POC\ScratchContents.txt -Value $Results;

但是,如果您的结果实际上非常大,您可能会担心内存使用情况。您实际上也可以为此目的使用System.IO.StreamWriter。通过切换到StreamWriter,我的流程速度提高了近两个数量级(从 12 小时到 20 分钟),并且只在我有大约 250 行要编写时才调用StreamWriter(这似乎是收支平衡点StreamWriter 的开销)。但我正在为大约 10,000 个用户和近 10 TB 的数据解析用户主目录和组共享的所有 ACL。您的任务可能没有那么大。

Here 是一个很好的博客来解释这个问题。

【讨论】:

  • 太棒了!我怀疑你写的一些东西;感谢您花时间解释!
【解决方案2】:

您是否至少有 PowerShell 3.0?如果你这样做了,你应该能够通过过滤掉文件来减少时间,因为你也会返回这些文件。

Get-ChildItem -Path $scratchpath -Recurse -Force -Directory | ...

目前您正在返回所有文件和文件夹,然后过滤掉带有$_.PSIsContainer 的文件,这会更慢。所以最终应该是这样的

Get-ChildItem -Path $scratchpath -Recurse -Force -Directory | 
    Where-Object{$_.CreationTime -lt $limit } |
    Select-Object -ExpandPropery FullName | 
    Add-Content C:\Data\eProposal\POC\ScratchContents.txt

【讨论】:

  • 感谢@Matt 的回复。剧本还没写完。不过,我注意到使用 -Directory 选项可以更快地增加 txt 文件的大小。看起来写完所有内容后,它正在等待什么(?)
  • 我也去掉了 -Force 和 -Recurse 因为它们与我的目的无关。脚本现在完成(大约 1-2 分钟)。不过,我想知道为什么不早点。
  • 你不需要recurse?您只是在寻找第一级文件夹?文件大小应该会增加得更快(因为我的代码会提高性能。)@mantlex