我正在尝试确定从给定文件夹递归填充带有文件路径的字符串数组的最有效方法。只需要文件路径;虽然不是必需的,但如果我可以在发现路径时对结果文件进行处理,那将是一个额外的好处。我在 Windows 10 上使用 PowerShell 版本 7.3.6,并且在包含许多子文件夹和许多其他文件类型的文件夹中搜索一个或多个文件扩展名。我偶然发现了以下链接中提出的问题,并尝试部分或全部实施每个建议的解决方案。这篇文章很长,因为我想尽可能少地留下猜测。我正在寻求有关如何提高本文中包含的代码片段的性能的反馈,以及有关可能产生更高效(及时)结果的任何其他方法的建议。
很明显,Get-ChildItem -Filter 参数不接受多个条件,而这在我的用例中是必需的。因此,一个潜在的解决方案是使用 -Include 和 -Exclude 参数的 Get-ChildItem,其实现如下:
[string]$SourcePath = "H:\Duplicate File Work\"
[string[]]$IncludeFilter = $null
[string[]]$FilePathCollection = $null
$IncludeFilter += "*.doc"
$IncludeFilter += "*.xls"
$IncludeFilter += "*.ppt"
$StopWatch = [System.Diagnostics.Stopwatch]::New()
$StopWatch.Start()
$FilePathCollection = Get-ChildItem -Path $SourcePath -Include $IncludeFilter -Recurse
Write-Host $FilePathCollection.Count
$StopWatch.Stop()
$StopWatch.Elapsed
上述代码在大约 4 分 27 秒内产生了 22,853 个结果,并且不允许我在发现每个文件时对其进行操作,因为字符串数组必须由 Get-ChildItem 完全填充后才能使用。另一个潜在的解决方案是 GetFiles,其实现如下:
[string]$SourcePath = "H:\Duplicate File Work\"
[string[]]$IncludeFilter = $null
[string[]]$FilePathCollection = $null
$IncludeFilter += "*.doc"
$IncludeFilter += "*.xls"
$IncludeFilter += "*.ppt"
$StopWatch = [System.Diagnostics.Stopwatch]::New()
$StopWatch.Start()
foreach ($Filter in $IncludeFilter) {
foreach ($File in [IO.Directory]::GetFiles($SourcePath, $Filter, "AllDirectories")) {
$FileAttributes = (Get-ItemProperty -Path $File).Attributes -split ", "
if ($FileAttributes -match "Hidden") {
Continue
}
else {
$FilePathCollection += $File
}
}
}
Write-Host $FilePathCollection.Count
$StopWatch.Stop()
$StopWatch.Elapsed
上述代码生成了 22,853 个结果,数量与 Get-ChildItem 相同,耗时约 6 分 6 秒,并允许我在发现每个文件时对其进行处理。请注意,添加了 if/else 块,允许在发现隐藏文件时对其进行筛选。EnumerateFiles 和 EnumerateFileSystemEntries 分别在 6 分 49 秒和 6 分 42 秒生成了与 GetFiles 相同的结果数。Get-ChildItem 未找到与 GetFiles、EnumerateFiles 和 EnumerateFileSystemEntries 不同的隐藏文件。就效率(及时性)而言,Get-ChildItem 似乎是显而易见的选择;但是,此方法不允许在发现文件时对其进行处理。为了确保 Get-ChildItem 和 GetFiles 找到的 22,853 个文件与 22,853 个文件相同,使用了以下代码:
[string]$SourcePath = "H:\Duplicate File Work\"
[string[]]$IncludeFilter = $null
[string[]]$FilePathCollection1 = $null
[string[]]$FilePathCollection2 = $null
[system.object]$CompareObjects = $null
[system.object]$StopWatch = [System.Diagnostics.Stopwatch]::New()
$IncludeFilter += "*.doc"
$IncludeFilter += "*.xls"
$IncludeFilter += "*.ppt"
$StopWatch.Start()
foreach ($Filter in $IncludeFilter) {
foreach ($File in [IO.Directory]::GetFiles($SourcePath, $Filter, "AllDirectories")) {
$FileAttributes = (Get-ItemProperty -Path $File).Attributes -split ", "
if ($FileAttributes -match "Hidden") {
Continue
}
else {
$FilePathCollection1 += $File
}
}
}
Write-Host $FilePathCollection1.Count
$StopWatch.Stop()
$StopWatch.Elapsed
$StopWatch.Reset()
$StopWatch.Start()
foreach ($File in Get-ChildItem -Path $SourcePath -Include $IncludeFilter -Recurse) {
$FilePathCollection2 += $File
}
Write-Host $FilePathCollection2.Count
$StopWatch.Stop()
$StopWatch.Elapsed
$CompareObjects = Compare-Object -ReferenceObject $FilePathCollection1 -DifferenceObject $FilePathCollection2
$CompareObjects | Out-GridView
检查 Compare-Object 结果 (无),发现 Get-ChildItem 找到的文件与 GetFiles 找到的文件之间没有差异。瞧!
现在回到这篇文章问题的初衷。是否有其他方法可以更有效地实现既定目标,或者有方法可以提高一种或多种给定方法的效率?
答案1
嗯,这有点像 hack,但你可以这样做
$文件=cmd/c cd C:\path`&dir/s/b*.exe*.pdf
17 秒内在我的 C 盘上找到 8k 个文件
编辑:询问有关可替换参数的问题,这首先会遇到困难,因为 powershell 保留了“&”
但可以绕过...创建一个 .cmd 文件 gather.cmd 在这个例子中,我们已经将批处理文件的参数尽可能地自然地传递出去(9)。两件事,这允许您传递 1-8 个扩展,少则一个,多则八个,如果您不传递额外的扩展,变量最终将为空白,不会影响执行,如果出于某种原因您需要超过 8,它能可以完成,但需要一些额外的代码,我们可以探索这是否确实显示出速度优势。
@cd %1 & dir /s /b %2 %3 %4 %5 %6 %7 %8 %9
然后使用 Invoke-Expression 从 powershell 调用它。
$path = "C:\Users\QUADWORD\Desktop"
$exts = "*.exe *.pdf"
$command = "cmd /c gather.cmd $path $exts"
$output = Invoke-Expression -Command $command
再次强调,这是个 hack,但是可能证明优势,没有测试就无法知道,我实际上很好奇,想知道与您的测试相比,这对我自己来说是更好/更差。
答案2
在 PowerShell 中,你可以使用以下命令处理命令的输出管道。
为了确保 Get-ChildItem 仅返回文件而不是目录,您可以向其传递 -File 开关参数。
为了使 Get-ChildItem 返回 FileSystemInfo 对象的相对文件路径,您可以向其传递 -Name 开关参数。
不幸的是,-File 和 -Name 开关参数都使得 Get-ChildItem 变慢,所以我在下面的代码示例中没有使用它们。
[string]$SourcePath = 'H:\Duplicate File Work\'
[string[]]$IncludeFilter = '*.doc','*.xls','*.ppt'
[string[]]$FilePathCollection =
Get-ChildItem -LiteralPath $SourcePath -Include $IncludeFilter -Recurse |
Where-Object -NOT PSIsContainer | # filter out directories
& { process {
# Work on a IO.FileInfo object stored in the $_ automatic variable.
# In this sample, just extract the full path.
$_.FullName
} }
EnumerateFiles/EnumerateFileSystemEntries 的性能可能会更好,但差距不会很大,而且代码的可读性也会受到影响。
请注意,问题中包含的行在$FilePathCollection += $File
每次添加项目时都会分配一个新数组。随着项目数量的增加,以这种方式收集项目的性能会非常差。