for循环中的多个Select个字符串来分隔文件

Multiple Select Strings in a for loop to separate files

我编写此脚本是为了根据 4 个不同的搜索条件搜索大量文本文件 (~100,000) 并导出到 4 个单独的文件,我认为按原样对每个文件执行所有 4 个搜索会更有效率加载 vs 像下面的第一次迭代那样进行 4 次完整搜索。由于我对 powershell 还很陌生,因此我可能遗漏了其他一些主要的低效率问题。

我将此脚本从第一个版本重写到第二个版本,但无法弄清楚如何像第一个版本那样让路径和数据一起显示。我正在努力在循环中引用对象,并将第二个版本拼凑在一起,这是有效的,但没有给我必要的文件路径。

似乎我只是遗漏了一两件让我朝着正确方向前进的小东西。预先感谢您的帮助

第一个版本:

Get-ChildItem -Filter *.txt -Path "\file\to\search" -Recurse | Select-String -Pattern "abc123" -Context 0,3 | Out-File -FilePath "\c:\out.txt"
Get-ChildItem -Filter *.txt -Path "\file\to\search2" -Recurse | Select-String -Pattern "abc124" -Context 0,3 | Out-File -FilePath "\c:\out2.txt"
Get-ChildItem -Filter *.txt -Path "\file\to\search3" -Recurse | Select-String -Pattern "abc125" -Context 0,3 | Out-File -FilePath "\c:\out3.txt"
Get-ChildItem -Filter *.txt -Path "\file\to\search4" -Recurse | Select-String -Pattern "abc126" -Context 0,3 | Out-File -FilePath "\c:\out4.txt"

输出:

  \file\that\was\found\example.txt:84:  abc123  
  \file\that\was\found\example.txt:90:  abc123 
  \file\that\was\found\example.txt:91:  abc123 
    

第二版:

##$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ Configuration $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

############################################ Global Parameters #############################################
$SearchPath="\file\to\search"
$ProgressFile=""\progress\file\ResultsCount.txt"
$records = 105325
##----------------------------------------- End Global Parameters -----------------------------------------

########################################### Search Parameters ##############################################
##Search Pattern 1
$Pattern1="abc123"
$SaveFile1="\c:\out.txt"

##Search Pattern 2
$Pattern2="abc124"
$SaveFile2="\c:\out2.txt"

##Search Pattern 3
$Pattern3= "abc125" 
$SaveFile3= "\c:\out3.txt"

##Search Pattern 4
$Pattern4= "abc126"
$SaveFile4="\c:\out4.txt"
 
##Search Pattern 5
$Pattern5= ""
$SaveFile5=""

##----------------------------------------- End Search Parameters ------------------------------------------
##$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ End of Config $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

############################### SCRIPT #####################################################################
                                                                                                          ## NOTES
                                                                                                          ## ------
##$files=Get-ChildItem -Filter *.txt -Path $SearchPath -Recurse                                             ## Set all files to variable ####  Long running, needs to be a better way #######
##$records=$files.count                                                                                     ## Set record #
Get-ChildItem -Filter *.txt -Path $SearchPath -Recurse | Foreach-Object {                                 ## loop through search folder
$i=$i+1                                                                                                   ## increment record
                                                                                                          ##
Get-Content $_.FullName | Select-String -Pattern $Pattern1 -Context 0,3 | Out-File -FilePath $SaveFile1   ## pattern1 search
Get-Content $_.FullName | Select-String -Pattern $Pattern2 | Out-File -FilePath $SaveFile2                ## pattern2 search
Get-Content $_.FullName | Select-String -Pattern $Pattern3 -Context 0,1 | Out-File -FilePath $SaveFile3   ## pattern3 search
Get-Content $_.FullName | Select-String -Pattern $Pattern4 -Context 0,1 | Out-File -FilePath $SaveFile4   ## pattern4 search
##Get-Content $_.FullName | Select-String -Pattern $Pattern5 -Context 0,1 | Out-File -FilePath $SaveFile5 ## pattern5  search (Comment out unneeded search lines like this one)
$progress ="Record $($i) of $($records)"                                                                  ## set progress
Write-Host "Record $($i) of $($records)"                                                                  ## Writes progress to window
$progress  | Out-File -FilePath $ProgressFile                                                             ## progress file
}                                                                                                         ##
############################################################################################################

输出:

abc123
abc123
abc123

编辑:此外,我正在尝试找出一种不必在记录数量中进行硬编码以获得体面的进度读数的好方法,我注释掉了我认为可行的方式(第 1 行和第 2 行脚本),但需要有一种比重新运行相同搜索两次更有效的方法,一次用于计数,一次用于 for 循环。

我会对您提供的任何运行时效率信息非常感兴趣。

[编辑 - 感谢 mklement0 指出关于速度和 -SimpleMatch 开关的错误。 [咧嘴一笑]]

Select-String cmdlet 将接受一个 -Path 参数...它是 FAR [我在想 Get-Content,不是Get-ChidItem] 比使用 Get-ChildItem 将文件提供给 S-S 更快。 [咧嘴一笑]

此外,-Pattern 参数接受正则表达式 OR 模式,如 Thing|OtherThing|YetAnotherThing - 如果您使用 -SimpleMatch 开关参数,它接受简单的字符串模式。

代码的作用...

  • 定义源目录
  • 定义文件规范
  • 将这两个加入通配符文件路径
  • 构建一个字符串模式数组以供使用
  • 使用要搜索的路径和字符串数组调用 Select-String
  • 使用 Group-Object 和计算的 属性 根据 .Line 属性 的最后部分对匹配项进行分组 S-S 调用
  • 将其保存到 $Var
  • 在屏幕上显示

那时,您可以使用每个 GroupInfo 到 select 的 .Name 属性 项目发送到每个文件并构建您的文件名.

代码 ...

$SourceDir = 'D:\Temp\zzz - Copy'
$FileSpec = '*.log'
$SD_FileSpec = Join-Path -Path $SourceDir -ChildPath $FileSpec

$TargetPatternList = @(
    'Accordion Cajun Zydeco'
    'better-not-be-there'
    'Piano Rockabilly Rowdy'
    )

$GO_Results = Select-String -Path $SD_FileSpec -SimpleMatch $TargetPatternList |
    Group-Object -Property {$_.Line.Split(':')[-1]}

$GO_Results

输出...

Count Name                      Group                                                                                                                                     
----- ----                      -----                                                                                                                                     
    6 Accordion Cajun Zydeco    {D:\Temp\zzz - Copy\Grouping-List_08-02.log:11:Accordion Cajun Zydeco, D:\Temp\zzz - Copy\Grouping-List_08-09.log:11:Accordion Cajun Zy...
    6 Bawdy Dupe Piano Rocka... {D:\Temp\zzz - Copy\Grouping-List_08-02.log:108:Bawdy Dupe Piano Rockabilly Rowdy, D:\Temp\zzz - Copy\Grouping-List_08-09.log:108:Bawdy...
    6 Bawdy Piano Rockabilly... {D:\Temp\zzz - Copy\Grouping-List_08-02.log:138:Bawdy Piano Rockabilly Rowdy, D:\Temp\zzz - Copy\Grouping-List_08-09.log:138:Bawdy Pian...
    6 Dupe Piano Rockabilly ... {D:\Temp\zzz - Copy\Grouping-List_08-02.log:948:Dupe Piano Rockabilly Rowdy, D:\Temp\zzz - Copy\Grouping-List_08-09.log:948:Dupe Piano ...
    6 Instrumental Piano Roc... {D:\Temp\zzz - Copy\Grouping-List_08-02.log:1563:Instrumental Piano Rockabilly Rowdy, D:\Temp\zzz - Copy\Grouping-List_08-09.log:1563:I...
    6 Piano Rockabilly Rowdy    {D:\Temp\zzz - Copy\Grouping-List_08-02.log:1781:Piano Rockabilly Rowdy, D:\Temp\zzz - Copy\Grouping-List_08-09.log:1781:Piano Rockabil...

请注意,.Group 包含来自 S-S 调用发出的匹配行的数组。您可以将其发送到您的输出文件。

这是我解决这个问题的方法,与 nice answer but with a foreach loop. I would recommend investing some time into researching the multi-threading options available on PowerShell in case you need to increase the performance of the script, you can look specifically at the ThreadJob module by Microsoft which is really easy to use or if you can't install modules due to some work policy, you can use Runspace 非常相似。

值得补充的是,您可以在 Select-String 上使用 -List 开关,这样脚本的性能会进一步提高:

-List
Only the first instance of matching text is returned from each input file. This is the most efficient way to retrieve a list of files that have contents matching the regular expression.

$map = @{
    abc123 = 'C:\out_abc123.txt'
    abc124 = 'C:\out_abc124.txt'
    abc125 = 'C:\out_abc125.txt'
}

$pattern = $map.Keys -join '|'

$match = foreach($file in Get-ChildItem *.txt)
{
    Select-String -LiteralPath $file.FullName -Pattern $pattern
}

$match | Group-Object { $_.Matches.Value } | ForEach-Object {
    $_.Group | Select-Object Path, LineNumber, Line | Out-File $map[$_.Name]
}

恭维答案@Santiago Squarzon and Lee_Dailey, I think you were actually on the good way yourself knowing that the Group-Object cmdlet is pretty expensive especially in memory usage as it chokes the PowerShell pipeline导致所有搜索结果堆积在内存中。

此外,Select-String cmdlet supports multiple (-SimpleMatch) patterns, where concatenating the search patters with an | (-join '|') will force you to use an (escaped) regular expression.

继续您的方法:
(请注意,在示例中,我使用自己的设置来搜索我的脚本文件)

$ProgressFile = '.\ResultsCount.txt'
$SearchRoot = '..\'
$Filter = '*.ps1'
$Searches = @{
    'Null'   = '.\Null.txt'
    'Test'   = '.\Test.txt'
    'Object' = '.\Object.txt'
}

$Files = Get-ChildItem -Filter $Filter -Path $SearchRoot -Recurse
$Total = $Files.count

$Searches.Values |ForEach-Object { Set-Content -LiteralPath $_ -Value '' }

$i = 0
ForEach ($File in $Files) {
    Get-Content -LiteralPath $File.FullName |
        Select-String @($Searches.Keys) -AllMatches |ForEach-Object {
            $Value = '{0}:{1}:{2}' -f $File.FullName, $_.LineNumber, $_
            Add-Content -LiteralPath $Searches[$_.Pattern] -Value $Value
        }
    'Record {0} of {1}' -f ++$i, $Total |Tee-Object -Append .\ProgressFile.txt
}

解释

  • $Searches = @{ ...
    将搜索模式与文件映射,您也可以使用 PSObject 列表来指定每个搜索(您可以在其中添加具有例如上下文 start/end 值等的列)

  • $Searches.Values |ForEach-Object { Set-Content -LiteralPath $_ -Value '' }
    清空结果文件(知道它们不是你不能使用的主流的一部分Add-Content

  • $i = 0
    不幸的是,没有使用 foreach 循环初始化的自动索引(但是,请参阅:#13772 Automatic variable for the pipeline index

  • Get-Content -LiteralPath $File.FullName
    将内容一次加载到内存中
    注1:这是一个字符串数组.
    注2: $Content 每次迭代都会重复使用,因此会覆盖前一个并将其从内存中卸载

  • Select-String @($Searches.Keys) -AllMatches |ForEach-Object {
    使用您(多个)定义的模式搜索字符串数组。 (如果您的搜索字符串包含特殊字符,您可以考虑使用 -SimpleMatch 参数。)
    注意: 很遗憾,您需要将 $Searches.Keys 嵌入 array subexpression operator @( ), for details see .Net issue: #56835 Make OrderedDictionaryKeyValueCollection implement IList

  • $Value = '{0}:{1}:{2}' -f $File.FullName, $_.LineNumber, $_
    构建结果输出字符串。
    注意: Select-String 的结果确实有一个(隐藏)LineNumber 和(匹配)Pattern 属性.

  • Add-Content -LiteralPath $Searches[$_.Pattern] -Value $Value
    将结果字符串添加到特定的映射输出文件。

  • 'Record {0} of {1}' -f $i++, $Total |Tee-Object -Append .\ProgressFile.txt
    Tee-Object 将进度写入标准输出(显示)以及特定文件。