2012-03-15 100 views
19

我有265個CSV文件,總記錄數超過400萬(行),需要在所有CSV文件中進行搜索和替換。我有我的PowerShell代碼下面,這是否一個片段,但它需要17分鐘,進行動作:在大型搜索/替換操作中PowerShell比較慢(比Python慢​​得多)?

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在文本操作方面只是一種更強大的編程語言?

回答

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) 
+0

在Python的情況下,但是,它仍然處理每個文件一氣呵成(它只是需要更多的代碼到那裏),所以我會想象,內存使用情況是「大約相同」......還是我錯過了什麼? :( – 2012-03-15 17:14:44

+0

@pst我沒有測試,但它看起來像's = f.read()'加載到內存中,你也可以使用'$ reader.ReadToEnd()'PowerShell做到這一點。 – 2012-03-15 17:23:52

+0

啊,我假設Get-Content是如何運作的: -/ – 2012-03-15 17:26:55

3

我不知道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」因爲你直接使用.NET的API,而不是類似的cmdlet,但他們把能力有些時候你需要它。

+0

但是正則表達式 - 對於任何簡單的非過度回溯正則表達式 - 通常具有非常快速的實現。像普通的字符串搜索一樣快(儘管它可能在某些情況下),但我懷疑它是17倍慢(在任何情況下,+1和差異和測試代碼。 – 2012-03-15 17:17:59

+0

@pst正則表達式可以在PowerShell中編譯,這應該會提供更好的性能。 '$ re =新對象正則表達式'\ w +','編譯'' – 2012-03-15 17:29:30

+0

感謝上面的測試代碼。我跑了它,它仍然需要很長時間才能完成任務。 – Keith 2012-03-15 23:26:38

5

我看到這個有很多:

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

的-replace運算將處理整個數組一次:

$content -replace $SearchStr, $ReplaceStr 

並且比在同一時間通過一個元素的迭代速度快了很多。我懷疑這樣做可能會讓你更接近蘋果與蘋果的比較。

+0

得到這個處理一個400MB的文本文件''替換'操作失敗:類型'System.OutOfMemoryException'的異常被拋出..'正在做一些比較測試。 – 2012-03-15 18:13:43

+0

http://stackoverflow.com/questions/9439210/how-can-i-make-this-powershell-script-parse-large-files-faster/9439750#9439750 – mjolinor 2012-03-15 18:33:32

+0

是的,閱讀文件的時間塊很多更適合大文件。 – 2012-03-15 19:04:56

2

你可能想嘗試使用以下命令:

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

此外,某些字符串可能需要轉義字符,因此你應該使用[正則表達式]逃出來生成與內置的轉義字符的字符串代碼會。看起來像:

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

其實,我現在面臨類似的問題。隨着我的新工作,我必須解析巨大的文本文件來根據特定標準提取信息。 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-command {}中以保持其「科學」。

此外,link,瘋狂讀..

+1

這與問題無關,並且在鏈接中:當然,powershell cmdlet速度較慢,它們獲取關於該文件的更多信息(如.Net DateTime和Length的創建時間)並構造PSCustomObjects,並且它們在他們下面有PSProviders的代碼。 .Net方法調用的'@()+ ='模式非常緩慢,從不推薦使用快速PowerShell。聲明C#編譯意味着PowerShell不是 - 它是由PS 3及以上版本的DLR引擎 - 但僅用於保存的腳本和函數,而不是從ISE運行的代碼或鍵入的代碼 – TessellatingHeckler 2017-12-15 09:18:10

+1

您的「Mary有一隻小羊羔」比較也有缺陷 - 兩種語言中的'.lower()'和'.upper()'調用不會更改字符串,它們會生成並返回一個新字符串。在Python中,當您生成一個值並且不使用它時,什麼都不會發生。在PowerShell中,當您生成一個值並且不會將其分配到任何位置時,它會轉到輸出管道 - 這意味着您的PowerShell腳本將通過更復雜的機制(管道和輸出格式化程序,而不是您的Python)打印三倍的字符串寫主機相當於'print()') – TessellatingHeckler 2017-12-15 09:29:32

+1

@TessellatingHeckler謝謝你。更新了示例腳本。同時將它們寫入.py和.ps1文件並同時運行: C:\ temp> $ time1 = get-date >> python.exe。\ mary.py >> $ time2 = get-date >> New-TimeSpan -Start $ time1 -End $ time2 和python在49.7秒內完成,我厭倦了等待PS ......但是,在越來越多的使用之後,我相信任何數據相關的活動,使用Py和操作,使用Ps。這兩種語言都很棒,易於使用,所以都喜歡它們。 – Alex 2017-12-16 22:26:50