在大型搜索/替换操作中,PowerShell很慢(比Python慢 得多)?

问题描述 投票:21回答:5

我有265个CSV文件,总记录(行数)超过400万,需要在所有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在文本操作方面只是一种更强大的编程语言?

python performance powershell replace
5个回答
11
投票

试试这个PowerShell脚本。它应该表现得更好。在缓冲流中读取文件时,RAM的使用也少得多。

$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
投票

我看到了很多:

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

-replace运算符将立即处理整个数组:

$content -replace $SearchStr, $ReplaceStr

并且比一次迭代一个元素要快得多。我怀疑这样做可能会让你更接近苹果对苹果的比较。


3
投票

我不懂Python,但看起来你在Python脚本中进行文字字符串替换。在Powershell中,-replace运算符是正则表达式搜索/替换。我会将Powershell转换为在字符串类上使用replace方法(或者回答原始问题,我认为你的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”,因为您直接使用.NET API而不是类似的cmdlet,但是当您需要时,它们会将该功能放在那里。


2
投票

您可能想尝试以下命令:

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

此外,某些字符串可能需要转义字符,因此您应该使用[regex] Escape生成内置转义字符的字符串。代码如下所示:

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

1
投票

实际上,我现在面临着类似的问题。在我的新工作中,我必须解析大量文本文件以根据特定标准提取信息。 powershell脚本(优化到边缘)需要4个小时才能返回完全处理过的csv文件。我们编写了另一个不到1小时的python脚本......

尽管我喜欢powershell,但我心碎了。为了您的娱乐,试试这个:Powershell:

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

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

蟒蛇:

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

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

并触发两个工作。您甚至可以封装在Measure-command {}中以使其“科学”。

另外,link,疯狂阅读..

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