用Ruby读取,编辑和写入文本文件
在Ruby中有没有一种好的方法来读取,编辑和写入文件?
在我的在线search中,我发现了一些东西,build议将它们全部读入数组,然后修改数组,然后写出所有内容。 我觉得应该有一个更好的解决scheme,特别是如果我正在处理一个非常大的文件。
就像是:
myfile = File.open("path/to/file.txt", "r+") myfile.each do |line| myfile.replace_puts('blah') if line =~ /myregex/ end myfile.close
其中, replace_puts
将写入当前行,而不是像当前那样写(写)下一行,因为指针位于行末(分隔符之后)。
那么每行匹配/myregex/
将被replace为'blah'。 显然我脑海中所涉及到的比这个要多一些,就处理而言,可以在一行中完成,但是想法是一样的 – 我想逐行阅读一个文件,然后编辑一些特定的行,当我完成时写出来。
也许有一种方法只是说“倒回到最后一个分隔符后”? 或者使用each_with_index
并通过行索引号写入某种方式? 尽pipe如此,我找不到任何这样的东西。
到目前为止,最好的解决scheme是逐行读取事务,将其写入新的(临时)文件行(可能已编辑),然后用新的临时文件覆盖旧文件并删除。 再次,我觉得应该有一个更好的方法 – 我不认为我应该创build一个新的1gig文件只是为了编辑现有的1GB文件中的一些行。
一般来说,在文件中间无法进行任意的编辑。 这不是Ruby的缺陷。 这是文件系统的一个局限性:大多数文件系统使文件最终增长或缩小变得简单而有效,而不是在开始或者中间。 所以,除非它的大小保持不变,否则你将不能重写一行。
有两个通用模型用于修改一堆线。 如果文件不是太大,就把它全部读入内存,修改它,然后写回去。 例如,将“Kilroy在这里”添加到文件的每一行的开头:
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
虽然简单,但是这种技术有一个危险:如果程序在写入文件的时候被中断,你将会失去部分或者全部。 它还需要使用内存来保存整个文件。 如果其中任何一个都是问题,那么你可能更喜欢下一个技术。
正如你注意到的,你可以写一个临时文件。 完成后,重命名临时文件,以便replaceinput文件:
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
)是primefaces的,所以重写的input文件将一次性popup。 如果程序中断,文件将被重写,否则不会。 它不可能被部分改写。
ensure
子句不是必须的:当Tempfile实例被垃圾收集时,文件将被删除。 但是,这可能需要一段时间。 ensure
块确保临时文件得到清理,而不必等待垃圾收集。
如果要逐行覆盖文件,则必须确保新行的长度与原始行的长度相同。 如果新行较长,则其一部分将被写入下一行。 如果新线路较短,旧线路的其余部分就停留在原来的位置。 临时文件解决scheme确实更安全。 但是,如果你愿意承担风险:
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
为了防止你使用Rails或Facets ,或者你依赖于Rails的ActiveSupport ,你可以使用atomic_write扩展名到File
:
File.atomic_write('path/file') do |file| file.write('your content') end
在幕后,这将创build一个临时文件,它将稍后移动到所需的path,照顾为您closures文件。
它进一步克隆现有文件的文件许可权,或者,如果不存在,则当前目录的权限。
你可以在一个文件的中间写入,但是你必须小心地保持string的长度不变,否则你会覆盖下面的一些文本。 我在这里使用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的文件上查看这些基准。 对于经典的方法,我使用了韦恩的技术。 基准testing是通过.bmbm方法完成的,因此caching文件不会有什么大问题。 在Windows 7上使用MRI Ruby 2.3.0完成testing。string被有效地replace,我检查了两种方法。
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_filereplace速度快了4倍。