for循环中的多个选择字符串以分隔文件

Posted

技术标签:

【中文标题】for循环中的多个选择字符串以分隔文件【英文标题】:Multiple Select Strings in a for loop to separate files 【发布时间】:2022-01-24 01:55:50 【问题描述】:

我编写了这个脚本来搜索大量文本文件 (~100,000) 的 4 个不同搜索条件并导出到 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 循环。

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

【问题讨论】:

-Pattern 与正则表达式兼容,您可以一次性使用| 加入单词。另请注意,在您的第二个示例中,由于-Append 不存在,文件被覆盖。 那不会将它们全部写入同一个输出文件吗?感谢 -append 提示! 您的第一个示例很好,因为在第二个示例中,所有文件都在同一管道(Get-Content (all txt files) > Select-String (reads them all and outputs all findings) > Out-File (captures all outputs from Select-String and saves them on a file))上处理,但是,每个文件都有一个外部循环每当发现文件被有效替换时进行迭代。希望这是有道理的。 这绝对是有道理的,也是为什么我不能得到一个好的计数,因为它一次只通过一个记录。我仍然对为什么我第二次构建的方式将输出更改为不显示文件路径感到困惑。 感谢 SantiagoSquarzon 和 @dugas 的帮助,感谢你们俩。 【参考方案1】:

[编辑 - 感谢 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 和计算属性按S-S 调用中.Line 属性的最后一部分对匹配项进行分组 将其保存到 $Var 在屏幕上显示

此时,您可以使用每个 GroupInfo.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 调用发出的匹配项的数组。您可以将其发送到您的输出文件。

【讨论】:

【参考方案2】:

这是我解决此问题的方法,与Lee_Dailey's 不错的答案非常相似,但带有foreach 循环。我建议花一些时间研究 PowerShell 上可用的多线程选项,以防您需要提高脚本的性能,您可以专门查看 Microsoft 的 ThreadJob 模块,它非常易于使用,或者如果您可以'由于某些工作政策,不安装模块,您可以使用Runspace。

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

-List 每个输入文件只返回匹配文本的第一个实例。这是检索内容与正则表达式匹配的文件列表的最有效方法。

$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]

【讨论】:

两个答案都非常有帮助,并且都得到了我的支持并进一步研究了它们的工作原理,但圣地亚哥帮助我在发布他深思熟虑的后续答案之前先让我的脚本正常运行,所以我必须给他功劳。 谢谢@Ryan,这两个答案同样有效,如果您正在考虑让多线程有机会从一些代码开始,如果您遇到困难,请再次发布,如果我看到我会帮助您问题:)【参考方案3】:

为了恭维@Santiago Squarzon 和Lee_Dailey 的答案,我认为您自己知道Group-Object cmdlet 非常昂贵,尤其是在内存使用方面非常昂贵,因为它阻塞了PowerShell pipeline,导致所有搜索结果要堆积在内存中。

此外,Select-String cmdlet 支持多种 (-SimpleMatch) 模式,其中将搜索模式与 | (-join '|') 连接将强制您使用(转义的)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 列表来指定每个搜索(您可以在其中添加具有例如上下文开始/结束值等的列)

$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 @( ) 中,有关详细信息,请参阅.Net问题:#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.txtTee-Object 会将进度写入标准输出(显示)以及特定文件。

【讨论】:

我已经更新了我的答案,因为我忘记了 Select-String 支持多种模式这一事实,这是您原来方法的另一个论据。

以上是关于for循环中的多个选择字符串以分隔文件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用for循环将文本文件中的一行字符串作为Bash中另一个脚本的单独变量传递[重复]

shell的for while读取文件写法和区别

for循环中的多个do命令:将字符串回显到文件然后重定向到命令窗口

在批处理文件中拆分以逗号分隔的字符串并在循环中调用 sqlplus 函数

java 从字符串中 以单个或多个空格进行分隔 提取字符串

C语言,输入一行文字,单词间以空格分隔,然后分离其中的单词按每行一个单词输出,程序有了,求解释