PowerShell работает медленно (намного медленнее, чем Python) в больших операциях поиска / замены? - PullRequest
21 голосов
/ 15 марта 2012

У меня 265 CSV-файлов с более чем 4 миллионами записей (строк), и мне нужно выполнить поиск и замену во всех CSV-файлах.Ниже приведен фрагмент моего кода PowerShell, который делает это, но для выполнения действия требуется 17 минут:

ForEach ($file in Get-ChildItem C:\temp\csv\*.csv) 
{
    $content = Get-Content -path $file
    $content | foreach {$_ -replace $SearchStr, $ReplaceStr} | Set-Content $file
}

Теперь у меня есть следующий код Python, который делает то же самое, но занимает менее 1 минутывыполнить:

import os, fnmatch

def findReplace(directory, find, replace, filePattern):
    for path, dirs, files in os.walk(os.path.abspath(directory)):
        for filename in fnmatch.filter(files, filePattern):
            filepath = os.path.join(path, filename)
            with open(filepath) as f:
                s = f.read()
            s = s.replace(find, replace)
            with open(filepath, "w") as f:
                f.write(s)

findReplace("c:/temp/csv", "Search String", "Replace String", "*.csv")

Почему метод Python намного эффективнее?Является ли мой код PowerShell неэффективным, или Python является более мощным языком программирования, когда речь идет о манипулировании текстом?

Ответы [ 5 ]

11 голосов
/ 15 марта 2012

Попробуйте этот скрипт PowerShell.Это должно работать намного лучше.Гораздо меньше использования оперативной памяти, поскольку файл читается в буферизованном потоке.

$reader = [IO.File]::OpenText("C:\input.csv")
$writer = New-Object System.IO.StreamWriter("C:\output.csv")

while ($reader.Peek() -ge 0) {
    $line = $reader.ReadLine()
    $line2 = $line -replace $SearchStr, $ReplaceStr
    $writer.writeline($line2)
}

$reader.Close()
$writer.Close()

Это обрабатывает один файл, но вы можете проверить производительность с ним и, если это более приемлемо, добавить его в цикл.*

В качестве альтернативы вы можете использовать Get-Content, чтобы прочитать несколько строк в память, выполнить замену, а затем записать обновленный фрагмент, используя конвейер PowerShell.

Get-Content "C:\input.csv" -ReadCount 512 | % {
    $_ -replace $SearchStr, $ReplaceStr
} | Set-Content "C:\output.csv"

Чтобы немного повысить производительность, вытакже можно скомпилировать регулярное выражение (-replace использует регулярные выражения), например:

$re = New-Object Regex $SearchStr, 'Compiled'
$re.Replace( $_ , $ReplaceStr )
5 голосов
/ 15 марта 2012

Я вижу это много:

$content | foreach {$_ -replace $SearchStr, $ReplaceStr} 

Оператор -replace будет обрабатывать весь массив сразу:

$content -replace $SearchStr, $ReplaceStr

и сделает это намного быстрее, чем итерация по одному элементувовремя.Я подозреваю, что это может приблизить вас к сравнению яблок с яблоками.

3 голосов
/ 15 марта 2012

Я не знаю Python, но похоже, что вы выполняете буквенные замены строк в скрипте Python. В Powershell оператор -replace является поиском / заменой регулярного выражения. Я бы преобразовал Powershell в использование метода замены в классе строк (или, чтобы ответить на исходный вопрос, я думаю, что ваш Powershell неэффективен).

ForEach ($file in Get-ChildItem C:\temp\csv\*.csv) 
{
    $content = Get-Content -path $file
    # look close, not much changes
    $content | foreach {$_.Replace($SearchStr, $ReplaceStr)} | Set-Content $file
}

РЕДАКТИРОВАТЬ После дальнейшего рассмотрения, я думаю, я вижу другое (возможно, более важное) отличие в версиях. Похоже, что версия Python читает весь файл в одну строку. Версия Powershell, с другой стороны, читает массив строк .

В справке по Get-Content упоминается параметр ReadCount, который может повлиять на производительность. Установка этого счетчика в -1, кажется, читает весь файл в один массив. Это будет означать, что вы передаете массив через конвейер вместо отдельных строк, но простое изменение в коде будет иметь дело с этим:

# $content is now an array
$content | % { $_ } | % {$_.Replace($SearchStr, $ReplaceStr)} | Set-Content $file

Если вы хотите прочитать весь файл в одну строку, как кажется в версии Python, просто вызовите метод .NET напрямую:

# now you have to make sure to use a FULL RESOLVED PATH
$content = [System.IO.File]::ReadAllText($file.FullName) 
$content.Replace($SearchStr, $ReplaceStr) | Set-Content $file

Это не совсем как "Powershell-y", так как вы используете API .NET напрямую вместо аналогичных командлетов, но они предоставляют эту возможность в те моменты, когда вам это нужно.

2 голосов
/ 05 октября 2012

Вы можете попробовать следующую команду:

gci C:\temp\csv\*.csv | % { (gc $_) -replace $SearchStr, $ReplaceStr | out-file $_}

Кроме того, некоторые строки могут требовать escape-символов, поэтому вы должны использовать [regex] Escape для генерации строк со встроенными escape-символами. Код будет выглядеть так:

gci C:\temp\csv\*.csv | % { (gc $_) -replace $([regex]::Escape($SearchStr)) $([regex]::Escape($ReplaceStr)) | out-file $_}
1 голос
/ 11 октября 2017

На самом деле, я столкнулся с подобной проблемой прямо сейчас.С моей новой работой я должен анализировать огромные текстовые файлы, чтобы получить информацию, основанную на определенных критериях.Сценарий powershell (оптимизированный до краев) занимает 4 часа, чтобы вернуть полностью обработанный CSV-файл.Мы написали еще один скрипт на python, который занял чуть менее 1 часа ...

Столько, сколько я люблю PowerShell, я был разбит сердцем.Для вашего удовольствия попробуйте следующее: Powershell:

$num = 0
$string = "Mary had a little lamb"

while($num -lt 1000000){
    $string = $string.ToUpper()
    $string = $string.ToLower()
    Write-Host $string
    $num++
}

Python:

num = 0
string = "Mary had a little lamb"

while num < 1000000:
    string = string.lower()
    string = string.upper()
    print(string)
    num+=1

и вызовите два задания.Вы даже можете инкапсулировать в Measure-команду {}, чтобы она оставалась «научной».

Кроме того, ссылка , сумасшедшее чтение ..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...