2013-06-26 90 views
0

我正在使用powershell腳本將數據追加到一堆文件的末尾。 每個文件都是50Mb左右的CSV文件(說2百萬行),大約有50個文件。用於CSV修改的Slow Powershell腳本

腳本我使用看起來像這樣:

$MyInvocation.MyCommand.path 

$files = ls *.csv 

foreach($f in $files) 
{ 
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($f) 
$year = $basename.substring(0,4) 

Write-Host "Starting" $Basename 

$r = [IO.File]::OpenText($f) 
while ($r.Peek() -ge 0) { 
    $line = $r.ReadLine() 
    $line + "," + $year | Add-Content $(".\DR_" + $basename + ".CSV") 
} 
$r.Dispose() 

} 

問題是,這是非常緩慢的。大概需要12個小時才能完成。 這不是非常複雜,所以我不會指望它需要很長時間才能運行。 我能做些什麼來加快速度?

回答

2

讀取和逐行寫入文件可能會有點慢。也許你的防病毒也會造成速度緩慢。使用Measure-Command可以查看腳本的哪些部分是較慢的部分。

作爲一般建議,寧可寫幾塊大塊而不是大塊小塊。您可以通過在StringBuilder中存儲一些內容並將其內容追加到輸出文件中,例如1000個處理過的行來實現。像這樣,

$sb = new-object Text.StringBuilder # New String Builder for stuff 
$i = 1 # Row counter 
while ($r.Peek() -ge 0) { 
    # Add formatted stuff into the buffer 
    [void]$sb.Append($("{0},{1}{2}" -f $r.ReadLine(), $year, [Environment]::NewLine)) 

    if(++$i % 1000 -eq 0){ # When 1000 rows are added, dump contents into file 
     Add-Content $(".\DR_" + $basename + ".CSV") $sb.ToString() 
     $sb = new-object Text.StringBuilder # Reset the StringBuilder 
    } 
} 
# Don't miss the tail of the contents 
Add-Content $(".\DR_" + $basename + ".CSV") $sb.ToString() 
+0

這是一個顯着的速度提升。謝謝。 – DX101

+0

但是每次都會產生額外的換行符。我已經解決了這個問題(就目前而言),只是沒有使用if部分將每100行轉儲到一個文件中,並將其全部寫入最後。 – DX101

0

不要進入.NET Framework靜態方法並在存在可以完成對象工作的cmdlet時構建字符串。收集您的數據,添加年份列,然後導出到您的新文件。你也在做大量的文件I/O,這也會讓你放慢速度。

這可能需要更多的內存。但它一次讀取整個文件,並立即寫入整個文件。它還假定您的CSV文件具有列標題。但它的很多更容易讓別人看到並理解到底發生了什麼(編寫腳本以便它們可以被讀取!)。

# Always use full cmdlet names in scripts, not aliases 
$files = get-childitem *.csv; 

foreach($f in $files) 
{ 
    #basename is a property of the file object in PowerShell, there's no need to call a static method 
    $basename = $f.basename; 
    $year = $f.basename.substring(0,4) 

    # Every time you use Write-Host, a puppy dies 
    "Starting $Basename"; 

    # If you've got CSV data, treat it as CSV data. PowerShell can import it into a collection natively. 
    $data = Import-Csv $f; 
    $exportData = @(); 
    foreach ($row in $data) { 
# Add a year "property" to each row object 
     $row |Add-Member -membertype NoteProperty -Name "Year" -Value $year; 
# Export the modified row to the output file 
     $row |Export-Csv -NoTypeInformation -Path $("r:\DR_" + $basename + ".CSV") -Append -NoClobber 
    } 
} 
+0

謝謝,一些評論非常豐富。但是,我原來的腳本看起來很像這樣。我改變它的原因是,它太餓了,太慢了。 實際上令人費解的是,運行這個腳本似乎只使用了一個GB的內存,只有一個50MB的CSV文件。任何想法爲什麼? – DX101

+0

內存量似乎過多。我基本上是把兩個*這個文件拷貝到內存中,但它仍然是一大堆內存。我現在做了一個編輯,將每行轉儲到磁盤上,而不是全部收集。這將是I/O上的性能降低,但內存使用量應該更少。您也可以與其他答案混合使用,並一次收集X個記錄,然後將這些記錄作爲一個組導出。 – alroc