Есть ли способ оптимизировать мою функцию Powershell для удаления совпадений с шаблонами из большого файла?

У меня есть большой текстовый файл (~ 20 тыс. строк, ~ 80 символов в строке). У меня также есть большой массив (~ 1500 элементов) объектов, содержащих шаблоны, которые я хочу удалить из большого текстового файла. Обратите внимание: если шаблон из массива появляется в строке входного файла, я хочу удалить всю строку, а не только шаблон.

Входной файл представляет собой CSVish со строками, похожими на:

A;AAA-BBB;XXX;XX000029;WORD;WORD-WORD-1;00001;STRING;2015-07-01;;010;   

Шаблон в массиве, который я ищу в каждой строке входного файла, напоминает

XX000029

часть строки выше.

Моя несколько наивная функция для достижения этой цели в настоящее время выглядит так:

function Remove-IdsFromFile {
  param(
    [Parameter(Mandatory=$true,Position=0)]
    [string]$BigFile,
    [Parameter(Mandatory=$true,Position=1)]
    [Object[]]$IgnorePatterns
  )

  try{
    $FileContent = Get-Content $BigFile
  }catch{
    Write-Error $_
  }

  $IgnorePatterns | ForEach-Object {
    $IgnoreId = $_.IgnoreId
    $FileContent = $FileContent | Where-Object { $_ -notmatch $IgnoreId }
    Write-Host $FileContent.count
  }
  $FileContent | Set-Content "CleansedBigFile.txt"
}

Это работает, но медленно.

Как я могу сделать это быстрее?


person GodEater    schedule 28.07.2015    source источник
comment
У вас есть образец шаблонов игнорирования?   -  person Matt    schedule 28.07.2015


Ответы (1)


function Remove-IdsFromFile {
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]$BigFile,
        [Parameter(Mandatory=$true,Position=1)]
        [Object[]]$IgnorePatterns
    )

    # Create the pattern matches
    $regex = ($IgnorePatterns | ForEach-Object{[regex]::Escape($_)}) -join "|"

    If(Test-Path $BigFile){
    $reader = New-Object  System.IO.StreamReader($BigFile)

    $line=$reader.ReadLine()
    while ($line -ne $null)
    {
        # Check if the line should be output to file
        If($line -notmatch $regex){$line | Add-Content "CleansedBigFile.txt"}

        # Attempt to read the next line. 
        $line=$reader.ReadLine()
    }

    $reader.close()

    } Else {
        Write-Error "Cannot locate: $BigFile"
    }
}

StreamReader — один из предпочтительных методов чтения больших текстовых файлов. Мы также используем регулярное выражение для создания строки шаблона для сопоставления на основе. В строке шаблона мы используем [regex]::Escape() в качестве меры предосторожности, если присутствуют управляющие символы регулярного выражения. Придется угадывать, так как мы видим только одну строку шаблона.

Если $IgnorePatterns можно легко преобразовать в строки, это должно работать нормально. Вот небольшой пример того, как выглядит $regex:

XX000029|XX000028|XX000027

Если $IgnorePatterns заполняется из базы данных, у вас может быть меньше контроля над этим, но, поскольку мы используем регулярное выражение, вы можете уменьшить этот набор шаблонов, действительно используя регулярное выражение (вместо простого большого альтернативного совпадения), например в моем примере выше. Вы можете уменьшить это, например, до XX00002[7-9].

Я не знаю, обеспечит ли само регулярное выражение повышение производительности с 1500 возможными. StreamReader здесь должен быть в центре внимания. Однако я запятнал воду, используя Add-Content для вывода, который также не получает никаких наград за быстроту (может использовать потоковую запись вместо этого).

Чтение и запись

Мне все еще нужно проверить это, чтобы убедиться, что это работает, но здесь используются только streamreader и streamwriter. Если это работает лучше, я просто заменю приведенный выше код.

function Remove-IdsFromFile {
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]$BigFile,
        [Parameter(Mandatory=$true,Position=1)]
        [Object[]]$IgnorePatterns
    )

    # Create the pattern matches
    $regex = ($IgnorePatterns | ForEach-Object{[regex]::Escape($_)}) -join "|"

    If(Test-Path $BigFile){
        # Prepare the StreamReader
        $reader = New-Object System.IO.StreamReader($BigFile)

        #Prepare the StreamWriter
        $writer = New-Object System.IO.StreamWriter("CleansedBigFile.txt")

        $line=$reader.ReadLine()
        while ($line -ne $null)
        {
            # Check if the line should be output to file
            If($line -notmatch $regex){$writer.WriteLine($line)}

            # Attempt to read the next line. 
            $line=$reader.ReadLine()
        }

        # Don't cross the streams!
        $reader.Close()
        $writer.Close()

    } Else {
        Write-Error "Cannot locate: $BigFile"
    }
}

Вам может понадобиться некоторое предотвращение ошибок для потоков, но, похоже, оно работает на месте.

person Matt    schedule 28.07.2015
comment
Я думал превратить его в одно огромное регулярное выражение, но не был уверен, что это сработает должным образом. Я попробую ваше решение и посмотрю. Кроме того, я не против создания вывода в памяти - я буду запускать это на сервере со 128 ГБ ОЗУ - я не боюсь нехватки памяти;) - person GodEater; 28.07.2015
comment
Только что протестировал первую версию, просто добавив $line к переменной массива, а затем выполнив один set-content в конце. На порядок быстрее исходной версии. Потрясающе - спасибо! - person GodEater; 28.07.2015