2010-12-09 73 views
44

在Ruby中是否有一種很好的方式來讀取,編輯和寫入文件?使用Ruby以行方式讀取,編輯和寫入文本文件

在我的在線搜索中,我找到了一些建議將它全部讀入數組,修改數組,然後寫出所有內容的東西。我覺得應該有一個更好的解決方案,特別是如果我正在處理一個非常大的文件。

喜歡的東西:

​​

replace_puts將當前行,而不是(上)寫的下一行,因爲它目前確實因爲指針是在該行的末尾(在分離器後寫了)。

因此,那麼匹配/myregex/的每一行都將被替換爲'blah'。很顯然,我所想的只是一點點處理,就處理而言,並且將在一行中完成,但這個想法是相同的 - 我想逐行讀取文件並編輯某些行,並且當我完成時寫出來。

也許有一種方法只是說「倒回到最後一個分隔符後」?或者通過某種方式使用each_with_index並通過行索引編號?不過,我找不到任何類似的東西。

到目前爲止,我所擁有的最佳解決方案是逐行讀取事物,將其寫入新的(臨時)文件行(可能已編輯),然後用新臨時文件覆蓋舊文件並刪除。再次,我覺得應該有一個更好的方式 - 我認爲我不應該創建一個新的1gig文件來編輯現有1GB文件中的某些行。

+0

如果您的代碼要讀取然後覆蓋會導致進程中途失敗,請考慮結果:您會冒着破壞文件的風險。 – 2010-12-10 00:29:49

+0

好吧,作爲後續問題:從命令行,你可以這樣做:ruby -pe「gsub(/ blah /,'newstuff')」whatev.txt。這就是我想要做的,但我不想在命令行上這樣做,我想把它放在更大的東西里面。任何人都可以在內部告訴我,那個命令正在做什麼,從而給出了逐行編輯文件的錯覺?它是寫入臨時文件還是使用數組?因爲它似乎很快處理相當大的文件,而不是迄今爲止提供的建議。 – Hsiu 2010-12-10 08:42:08

+0

這是一個很好的問題。你能把它變成一個新的問題嗎?這使得其他人更容易看到並回答它。另外,如果這個問題得到了您的滿意答覆,您能否接受該答案?謝謝! – 2010-12-16 23:20:58

回答

6

如果要逐行覆蓋文件,則必須確保新行的長度與原始行的長度相同。如果新行較長,則其一部分將被寫入下一行。如果新線較短,舊線的其餘部分就停留在原來的位置。 臨時文件解決方案確實更安全。但是,如果你願意承擔風險:

File.open('test.txt', 'r+') do |f| 
    old_pos = 0 
    f.each do |line| 
     f.pos = old_pos # this is the 'rewind' 
     f.print line.gsub('2010', '2011') 
     old_pos = f.pos 
    end 
end 

如果行的大小是變化的,這是一個可能性:

File.open('test.txt', 'r+') do |f| 
    out = "" 
    f.each do |line| 
     out << line.gsub(/myregex/, 'blah') 
    end 
    f.pos = 0      
    f.print out 
    f.truncate(f.pos)    
end 
62

在一般情況下,有沒有辦法使任意編輯在中間的文件。這不是Ruby的缺陷。這是文件系統的一個限制:大多數文件系統使文件最後增長或縮小的過程變得簡單而高效,而不是在開始或中間。因此,除非它的大小保持不變,否則您將無法重寫一條線。

有兩種修改一堆線條的通用模型。如果文件不是太大,只需將它全部讀入內存,修改它並將其寫回。例如,添加「基爾羅伊在這裏」到一個文件中的每一行的開頭:

path = '/tmp/foo' 
lines = IO.readlines(path).map do |line| 
    'Kilroy was here ' + line 
end 
File.open(path, 'w') do |file| 
    file.puts lines 
end 

雖然簡單,這種技術有一個危險:如果在寫入文件的程序被中斷,你會失去一部分或全部。它還需要使用內存來保存整個文件。如果其中任何一個都是問題,那麼你可能更喜歡下一個技術。

如您所記,您可以寫入臨時文件。完成後,重命名臨時文件以替換輸入文件:

require 'tempfile' 
require 'fileutils' 

path = '/tmp/foo' 
temp_file = Tempfile.new('foo') 
begin 
    File.open(path, 'r') do |file| 
    file.each_line do |line| 
     temp_file.puts 'Kilroy was here ' + line 
    end 
    end 
    temp_file.close 
    FileUtils.mv(temp_file.path, path) 
ensure 
    temp_file.close 
    temp_file.unlink 
end 

由於重命名(FileUtils.mv)是原子時,重寫輸入文件將流行到存在的一次。如果程序中斷,文件將被重寫,否則不會。它不可能被部分改寫。

ensure子句不是絕對必要的:當Tempfile實例被垃圾收集時,文件將被刪除。但是,這可能需要一段時間。 ensure塊確保臨時文件被清理乾淨,而不必等待它被垃圾收集。

1

萬一你是使用Rails或Facets,或你,否則依靠Rails的ActiveSupport,您可以使用atomic_write擴展File

File.atomic_write('path/file') do |file| 
    file.write('your content') 
end 

在幕後,這將創建一個臨時文件,它會稍後移動到所需的路徑,照顧爲您關閉文件。

它進一步克隆現有文件或當前目錄的文件權限(如果沒有)。

0

你可以寫在一個文件的中間,但你必須小心,以保持字符串的長度覆蓋相同,否則你會覆蓋下面的一些文本。我在這裏使用File.seek給出了一個例子,IO :: SEEK_CUR給出了文件指針的當前位置,在剛剛讀取的行的末尾,+1表示行末尾的CR字符。

look_for  = "bbb" 
replace_with = "xxxxx" 

File.open(DATA, 'r+') do |file| 
    file.each_line do |line| 
    if (line[look_for]) 
     file.seek(-(line.length + 1), IO::SEEK_CUR) 
     file.write line.gsub(look_for, replace_with) 
    end 
    end 
end 
__END__ 
aaabbb 
bbbcccddd 
dddeee 
eee 

執行後,在腳本結尾處,您現在有以下內容,而不是您想到的內容。

aaaxxxxx 
bcccddd 
dddeee 
eee 

考慮到這一點,使用這種技術的速度比經典的「讀取和寫入新文件」方法要好得多。 在音樂數據大小爲1.7 GB的文件上查看這些基準。 對於經典的方法,我使用了韋恩的技術。 基準測試是通過.bmbm方法完成的,因此文件的緩存功能沒有什麼大不了的。在Windows 7上使用MRI Ruby 2.3.0完成測試。 字符串被有效替換,我檢查了兩種方法。

require 'benchmark' 
require 'tempfile' 
require 'fileutils' 

look_for  = "Melissa Etheridge" 
replace_with = "Malissa Etheridge" 
very_big_file = 'D:\Documents\muziekinfo\all.txt'.gsub('\\','/') 

def replace_with file_path, look_for, replace_with 
    File.open(file_path, 'r+') do |file| 
    file.each_line do |line| 
     if (line[look_for]) 
     file.seek(-(line.length + 1), IO::SEEK_CUR) 
     file.write line.gsub(look_for, replace_with) 
     end 
    end 
    end 
end 

def replace_with_classic path, look_for, replace_with 
    temp_file = Tempfile.new('foo') 
    File.foreach(path) do |line| 
    if (line[look_for]) 
     temp_file.write line.gsub(look_for, replace_with) 
    else 
     temp_file.write line 
    end 
    end 
    temp_file.close 
    FileUtils.mv(temp_file.path, path) 
ensure 
    temp_file.close 
    temp_file.unlink 
end 

Benchmark.bmbm do |x| 
    x.report("adapt   ") { 1.times {replace_with very_big_file, look_for, replace_with}} 
    x.report("restore  ") { 1.times {replace_with very_big_file, replace_with, look_for}} 
    x.report("classic adapt ") { 1.times {replace_with_classic very_big_file, look_for, replace_with}} 
    x.report("classic restore") { 1.times {replace_with_classic very_big_file, replace_with, look_for}} 
end 

這給了

Rehearsal --------------------------------------------------- 
adapt    6.989000 0.811000 7.800000 ( 7.800598) 
restore   7.192000 0.562000 7.754000 ( 7.774481) 
classic adapt 14.320000 9.438000 23.758000 (32.507433) 
classic restore 14.259000 9.469000 23.728000 (34.128093) 
----------------------------------------- total: 63.040000sec 

         user  system  total  real 
adapt    7.114000 0.718000 7.832000 ( 8.639864) 
restore   6.942000 0.858000 7.800000 ( 8.117839) 
classic adapt 14.430000 9.485000 23.915000 (32.195298) 
classic restore 14.695000 9.360000 24.055000 (33.709054) 

所以in_file中更換了快4倍。