【问题标题】:Searching many large text files in Powershell在 Powershell 中搜索许多大型文本文件
【发布时间】:2012-08-27 03:43:07
【问题描述】:

我经常需要在一个目录中搜索服务器日志文件,该目录可能包含 50 个或更多文件,每个文件超过 200 MB。我在 Powershell 中编写了一个函数来执行此搜索。它查找并提取给定查询参数的所有值。它适用于单个大文件或一组小文件,但完全适用于上述情况,即大文件目录。

该函数接受一个参数,该参数由要搜索的查询参数组成。

在伪代码中:

Take parameter (e.g. someParam or someParam=([^& ]+))
Create a regex (if one is not supplied)
Collect a directory list of *.log, pipe to Select-String
For each pipeline object, add the matchers to a hash as keys
Increment a match counter
Call GC
At the end of the pipelining: 
if (hash has keys) 
    enumerate the hash keys, 
    sort and append to string array
    set-content the string array to a file 
    print summary to console
    exit
else
    print summary to console
    exit

这是文件处理的精简版。

$wtmatches = @{};
gci -Filter *.log | Select-String -Pattern $searcher |       
%{ $wtmatches[$_.Matches[0].Groups[1].Value]++; $items++; [GC]::Collect(); }

我只是在使用一种旧的 perl 技巧,通过将找到的项目设为哈希键来对它们进行重复数据删除。也许,这是一个错误,但处理的典型输出最多约为 30,000 个项目。更常见的是,找到的项目在数千个范围内。据我所知,散列中的键数不会影响处理时间,而是文件的大小和数量会破坏它。我最近绝望地扔了GC,它确实有一些积极的影响,但它是微不足道的。

问题在于,对于大型文件的大量集合,处理过程会在大约 60 秒内将 RAM 池吸干。有趣的是,它实际上并没有使用很多 CPU,但是有很多易失性存储正在进行。一旦 RAM 使用率上升 90% 以上,我就可以打卡出去看电视了。完成处理以生成具有 15,000 或 20,000 个唯一值的文件可能需要数小时。

我想要提高效率的建议和/或建议,即使这意味着使用不同的范例来完成处理。我带着我所知道的去了。我几乎每天都使用这个工具。

哦,我致力于使用 Powershell。 ;-) 这个函数是我为我的工作编写的完整模块的一部分,因此,Python、perl 或其他有用语言的建议在这种情况下没有用。

谢谢。

mp

更新: 使用 latkin 的 ProcessFile 函数,我使用以下包装器进行测试。他的功能比我原来的快几个数量级。

function Find-WtQuery {

<#
 .Synopsis
  Takes a parameter with a capture regex and a wildcard for files list.

 .Description
  This function is intended to be used on large collections of large files that have
  the potential to take an unacceptably long time to process using other methods. It
  requires that a regex capture group be passed in as the value to search for.

 .Parameter Target
  The parameter with capture group to find, e.g. WT.z_custom=([^ &]+).

 .Parameter Files
  The file wildcard to search, e.g. '*.log'

 .Outputs
  An object with an array of unique values and a count of total matched lines.
#>

        param(
        [Parameter(Mandatory = $true)] [string] $target,
        [Parameter(Mandatory = $false)] [string] $files
    )

    begin{
        $stime = Get-Date
    }
    process{
        $results = gci -Filter $files | ProcessFile -Pattern $target  -Group 1;
    }
    end{
        $etime = Get-Date;
        $ptime = $etime - $stime;
        Write-Host ("Processing time for {0} files was {1}:{2}:{3}." -f (gci   
    -Filter $files).Count, $ptime.Hours,$ptime.Minutes,$ptime.Seconds);
        return $results;
    }
}

输出:

clients:\test\logs\global
{powem} [4] --> Find-WtQuery -target "WT.ets=([^ &]+)" -files "*.log"
Processing time for 53 files was 0:1:35.

感谢所有 cmets 和帮助。

【问题讨论】:

  • 如果您最终去一个 powershell 专家站点来解决这个问题,请在此处发布您的最终解决方案(并接受它以获得代表点!)。有趣的问题(对不起,我建议使用 shell 工具:->)。祝你好运。
  • 我在Python中做过这种处理,实际上我为它写了一个lib。使用 Python 时,我遇到了相反的问题,它占用了 CPU。专用的二进制工具可能是最好的解决方案,但我只是喜欢编写脚本。避免 CPU/存储问题并非易事。谢谢。

标签: powershell full-text-search


【解决方案1】:

这是一个有望加速并减少文件处理部分的内存影响的函数。它将返回一个具有 2 个属性的对象:匹配的总行数,以及来自指定匹配组的唯一字符串的排序数组。 (从您的描述看来,您并不真正关心每个字符串的计数,只关心字符串值本身)

function ProcessFile
{
   param(
      [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
      [System.IO.FileInfo] $File,

      [Parameter(Mandatory = $true)]
      [string] $Pattern,

      [Parameter(Mandatory = $true)]
      [int] $Group
   )

   begin
   {
      $regex = new-object Regex @($pattern, 'Compiled')
      $set = new-object 'System.Collections.Generic.SortedDictionary[string, int]'
      $totalCount = 0
   }

   process
   {
      try
      {
        $reader = new-object IO.StreamReader $_.FullName

        while( ($line = $reader.ReadLine()) -ne $null)
        {
           $m = $regex.Match($line)
           if($m.Success)
           {
              $set[$m.Groups[$group].Value] = 1      
              $totalCount++
           }
        }
      }
      finally
      {
         $reader.Close()
      }
   }

   end
   {
      new-object psobject -prop @{TotalCount = $totalCount; Unique = ([string[]]$set.Keys)}
   }
}

你可以这样使用它:

$results = dir *.log | ProcessFile -Pattern 'stuff (capturegroup)' -Group 1
"Total matches: $($results.TotalCount)"
$results.Unique | Out-File .\Results.txt

【讨论】:

  • 我将此响应标记为答案,因为它是第一个开箱即用的并且完全解决了我的问题。我为此函数编写了一个包装器并对其进行计时,它在 00:01:35 处理了 53 个包含 180 万个匹配项的文件。繁荣!非常感谢您的帮助,我真的很感激。
  • 哦,我不关心给定值出现的次数是正确的。我只是在删除重复的值列表之后。
【解决方案2】:

如果您想在 PowerShell 中执行此操作而不使用某些专用工具,则可以采用 IMO @latkin 的方法。不过,我做了一些更改,以使命令在接受管道输入方面发挥得更好。我还修改了正则表达式以搜索特定行上的所有匹配项。这两种方法都不会搜索多行,尽管只要模式只跨越几行,这种情况就很容易处理。这是我对该命令的看法(将其放入名为 Search-File.ps1 的文件中):

[CmdletBinding(DefaultParameterSetName="Path")]
param(
    [Parameter(Mandatory=$true, Position=0)]
    [ValidateNotNullOrEmpty()]
    [string]
    $Pattern,

    [Parameter(Mandatory=$true, Position=1, ParameterSetName="Path", 
               ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true,
               HelpMessage="Path to ...")]
    [ValidateNotNullOrEmpty()]
    [string[]]
    $Path,

    [Alias("PSPath")]
    [Parameter(Mandatory=$true, Position=1, ParameterSetName="LiteralPath", 
               ValueFromPipelineByPropertyName=$true,
               HelpMessage="Path to ...")]
    [ValidateNotNullOrEmpty()]
    [string[]]
    $LiteralPath,

    [Parameter()]
    [ValidateRange(0, [int]::MaxValue)]
    [int]
    $Group = 0
)

Begin 
{ 
    Set-StrictMode -Version latest 
    $count = 0
    $matched = @{}
    $regex = New-Object System.Text.RegularExpressions.Regex $Pattern,'Compiled'
}

Process 
{
    if ($psCmdlet.ParameterSetName -eq "Path")
    {
        # In the -Path (non-literal) case we may need to resolve a wildcarded path
        $resolvedPaths = @($Path | Resolve-Path | Convert-Path)
    }
    else 
    {
        # Must be -LiteralPath
        $resolvedPaths = @($LiteralPath | Convert-Path)
    }

    foreach ($rpath in $resolvedPaths) 
    {
        Write-Verbose "Processing $rpath"

        $stream = new-object System.IO.FileStream $rpath,'Open','Read','Read',4096
        $reader = new-object System.IO.StreamReader $stream
        try
        {
            while (($line = $reader.ReadLine())-ne $null)
            {
                $matchColl = $regex.Matches($line)
                foreach ($match in $matchColl)
                {
                    $count++
                    $key = $match.Groups[$Group].Value
                    if ($matched.ContainsKey($key))
                    {
                        $matched[$key]++
                    }
                    else
                    {
                        $matched[$key] = 1;
                    }
                }
            }
        }
        finally
        {
            $reader.Close()
        }
    }
}

End
{
    new-object psobject -Property @{TotalCount = $count; Matched = $matched}
}

我在我的 IIS 日志目录(8.5 GB 和 ~1000 个文件)上运行此命令,以查找所有日志中的所有 IP 地址,例如:

$r = ls . -r *.log | C:\Users\hillr\Search-File.ps1 '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'

这在我的系统上花费了 27 分钟,找到了 54356330 个匹配项:

$r.Matched.GetEnumerator() | sort Value -Descending | select -f 20


Name                           Value
----                           -----
xxx.140.113.47                 22459654
xxx.29.24.217                  13430575
xxx.29.24.216                  13321196
xxx.140.113.98                 4701131
xxx.40.30.254                  53724

【讨论】:

  • 基思,感谢您的更新。我会用它。你们俩真的在PS上带我去学校。 ;-) 我每天都在工作中使用它,并且是我的牛人的“传道者”,但每个人都害怕 CLI。 (“什么——我必须放开鼠标和打字?”)好吧,公平地说,很难将学习新工具融入繁忙的日程中。再次感谢。哦,是的,我使用 PSCX,-- 也谢谢你。
  • @naugiedoggie - RE PSCX - 谢谢!听到用户的声音总是很高兴,尤其是快乐的用户。 :-)
  • @KeithHill 你好,Keith,我尝试了一个 3GB 的大文件,不幸的是,当 powershell 达到约 4GB 时,我得到了 outOfMemoryException。计算机仍然有很多可用空间,因为它有 16GB 的 RAM,并且没有其他大型进程正在运行。有没有办法使用这个或类似的东西来只在一个大文件中搜索?还有可能在字符串中找到字符串,以便如果搜索值为“StringToFind”并且文件包含“somethingStringToFindSomething”,它也会被捕获?任何帮助都非常感谢,谢谢!
  • 因为它达到了 4GB,应该在 64 位进程中运行。最大数组大小为 2GB,所以也许您遇到了这个问题。您是否期望与您的 3GB 文件有很多匹配项?要查看占用空间的是否是 $matched 变量,请注释掉 $matched[$key] = 1 行并查看 mem 的使用情况。顺便说一句,正则表达式应该根据您指定正则表达式模式的方式在字符串中找到一个字符串。默认情况下,StringToFind 会在您指定的较大字符串中找到它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多