PowerShell中递归获取指定扩展名文件路径的高效方法选型及优化问询
我现在正琢磨着找个最高效的方式,从指定文件夹递归获取文件路径,把这些路径填充到字符串数组里。我的需求很明确:只需要文件路径就行,如果能在发现文件的同时就对它们做些处理,那就是额外的惊喜了。我目前用的是Windows 10系统上的PowerShell 7.3.6,要处理的目录里有超多子文件夹,还有各种类型的文件,我只需要筛选出指定几种扩展名的文件。
我之前试过几种方法,现在想请教大家:有没有办法优化这些方法的性能,或者还有其他更高效的实现方式?下面是我试过的几种方案和测试情况:
方案一:Get-ChildItem结合-Include参数
因为Get-ChildItem的-Filter参数不支持多条件筛选,所以我用了-Include参数来实现多扩展名过滤,代码如下:
[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
测试结果:找到了22853个文件,耗时大约4分27秒。但这个方法的问题是,必须等Get-ChildItem把所有文件路径都收集完、填充到数组里之后,才能对文件进行处理,没办法做到边发现边处理。
方案二:使用.NET的[IO.Directory]::GetFiles()
我还试了用.NET类库的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
测试结果:同样找到了22853个文件,但耗时约6分6秒。不过这个方法的优点是,能在发现文件的同时就进行处理——比如我在代码里加了一段逻辑,用来过滤掉隐藏文件。
另外我还试了EnumerateFiles和EnumerateFileSystemEntries方法,它们找到的文件数量和GetFiles一样,但耗时更长,分别是6分49秒和6分42秒。
这里要提一下:Get-ChildItem默认不会搜索隐藏文件,而这几个.NET方法会,所以我特意加了隐藏文件的过滤逻辑,保证两种方法的结果一致。
验证结果一致性
为了确认Get-ChildItem和GetFiles找到的是完全相同的文件,我写了一段对比代码:
[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
对比结果显示,两种方法找到的文件完全一致,没有差异。
现在回到我的核心问题:有没有其他更高效的方法能实现我的需求?或者有没有办法优化现有的这些方案,让它们的执行速度更快?
备注:内容来源于stack exchange,提问作者Don

