2010-12-09 74 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倍。