使用 Powershell 运行空间搜索大量文件 (.XML)

问题描述 投票:0回答:1

我有一个脚本,可以在大量文件中查找正则表达式,例如地址或电话号码。我目前的脚本作为一项工作运行并工作,但速度非常慢。

目前我的开始工作方法按预期进行,一切都很缓慢。我正在寻找加快速度并更快返回结果的方法。如果可以的话

在浏览了各种帮助后,我冒险进入了 powershell 中的 Runspaces 世界。下面是我整理的代码,并简要了解了 Runspaces 的使用。

我的问题是如何使用运行空间,以便并行运行的 Get-Childitem 请求不会跨多个运行空间扫描同一文件。如果这可能的话?

我创建了 20,000 个包含垃圾的文件,并手动编辑了 2 个带有“番茄酱!”一词的文件里面。

10k 个文件是 .xml 10k 个文件是 .txt

我尝试不使用 PS v7 -parralel 参数,因为我想将我的脚本/GUI 交给其他非 IT 人员且不会安装高于 ISE 的员工

powershell 在大量文件中快速搜索短语

$Finished.text = 'Working.....' 

#Get list of files to search through
$path = "C:\intel\spam"
Push-Location $path
$FILES = Get-ChildItem -filter *.XML -File

### 5 Runspace limit
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1,5)
$RunspacePool.ApartmentState = "MTA"
$RunspacePool.Open()
$runspaces = @()

# Setup scriptblock
$scriptblock = {
   Param (
    [object]$files
   )
    foreach($file in $files){
  $test = select-string  -Path $file -Pattern 'KETCHUP!' -List | select-object FileName,Path
  
  if($test)
{
add-content -Path 'C:\intel\matches.txt' -Value $test.Filename
}
    }
                }


Write-Output "Starting search..."

       $runspace = [PowerShell]::Create()
       [void]$runspace.AddScript($scriptblock)
       [void]$runspace.AddArgument($FILES) # <-- Send files to be searched
       $runspace.RunspacePool = $RunspacePool

    $AsyncObject = $runspace.BeginInvoke() 

# Wait for runspaces to complete
while ($runspaces.Status.IsCompleted -notcontains $true) {}

# Cleanup runspaces 
foreach ($runspace in $runspaces ) { 
    $runspace.Pipe.EndInvoke($runspace.Status) 
    $runspace.Pipe.Dispose()
}

# Cleanup runspace pool
$RunspacePool.Close() 
$RunspacePool.Dispose()


  $Data = $runspace.EndInvoke($AsyncObject)

Pop-Location 

powershell search get-childitem runspace select-string
1个回答
0
投票

我的问题是如何使用运行空间,以便 并行运行的 Get-Childitem 请求将不会扫描 跨多个运行空间的同一文件。如果这可能的话?

这确实归结为逻辑,那就是分解文件以分块搜索,这是完全可行的。它的工作方式更像是这样。假设您有 8 文件和假设的 2-核心 CPU:

[File1] [File2] [File3] [File4] [File5] [File6] [File7] [File8]     <- All files from Get-ChildItem

确定chunk_size(在这个假设场景中为4,因为8文件除以2核心为4)后,代码会将这些文件分成块:

Chunk 1: [File1] [File2] [File3] [File4]
Chunk 2: [File5] [File6] [File7] [File8]

该除法将存储在

$file_chunks
ArrayList:

$file_chunks:
Index 0: [File1] [File2] [File3] [File4]
Index 1: [File5] [File6] [File7] [File8]

现在,当并行处理开始时,每个 CPU 核心(或运行空间)都会拾取一个块:

CPU Core 1 (Runspace 1): Processing [File1] [File2] [File3] [File4]
CPU Core 2 (Runspace 2): Processing [File5] [File6] [File7] [File8]

每个核心都处理自己的文件子集,从而实现更快的并行处理。

完成此操作后,您可以创建更强大的解决方案,例如以更频繁的方式重用它的函数:)

function Search-Files {
    Param(
        [Parameter()]
        [string]$Path,

        [Parameter()]
        [string]$Filter,

        [Parameter()]
        [string]$Pattern
    )

    $runspace_pool = [runspacefactory]::CreateRunspacePool(1, [Environment]::ProcessorCount) 
    $runspace_pool.Open()

    $runspaces = [System.Collections.ArrayList]::new()

    $files = Get-ChildItem -Path $path -Filter $filter -File
    $file_chunks = [System.Collections.ArrayList]::new()
    $chunk_size  = [Math]::Ceiling($files.Count / [Environment]::ProcessorCount)

    0..([Environment]::ProcessorCount - 1) | ForEach-Object {
        $start = $_ * $chunk_size
        $end = $start + $chunk_size - 1
        $null = $file_chunks.Add($files[$start..$end])
    }

    $scriptblock = {
        Param($files, $pattern)

        $results = [System.Collections.ArrayList]::new()
        foreach ($file in $files) 
        {
            try 
            {
                $reader = [System.IO.File]::OpenText($file.FullName)
                while ($reader.Peek() -ge 0) 
                {
                    $line = $reader.ReadLine()
                    if ($line -match $pattern) 
                    {
                        $null = $results.Add($file.FullName)
                        break
                    }
                }
            } 
            finally 
            {
                if ($reader) 
                {
                    $reader.Dispose()
                }
            }
        }
        return $results
    }

    foreach ($chunk in $file_chunks) 
    {
        $runspace = [powershell]::Create().AddScript($scriptblock).AddArgument($chunk).AddArgument($pattern)
        $runspace.RunspacePool = $runspace_pool
        $null = $runspaces.Add([PSCustomObject]@{
            pipe = $runspace
            status = $runspace.BeginInvoke()
        })
    }

    $all_results = foreach ($runspace in $runspaces) 
    {
        $runspace.pipe.EndInvoke($runspace.status)
    }

    $runspace_pool.Close()
    $runspace_pool.Dispose()

    return $all_results
}

# Usage
$path = "."
$filter = "*.txt"
$pattern = "KETCHUP!"
$results = Search-Files -path $path -filter $filter -pattern $pattern

$results | ForEach-Object { Add-Content -Path 'C:\intel\matches.txt' -Value $_ }

您必须注意

[System.IO.File]::OpenText($file.FullName)
而不是
Select-String
上的替换。使用
System.IO.File
类可以更有效地使用内存,因为它逐行读取文件,这对于大文件特别有利。相比之下,
Select-String
将整个文件加载到内存中,这可能会降低性能并且更占用内存。逐行方法还允许在找到匹配项后提前退出,从而进一步优化搜索过程。

© www.soinside.com 2019 - 2024. All rights reserved.