我想将一个txt文件拆分为多个文件,其中每个文件包含的大小不超过5Mb。我知道有工具可以做到这一点,但我需要这一点作为一个项目,并想用Ruby来做。此外,如果可能的话,我更喜欢在块上下文中使用File.open来做这件事,但我失败得很惨:o(
#!/usr/bin/env ruby
require 'pp'
MAX_BYTES = 5_000_000
file_num = 0
bytes = 0
File.open("test.txt", 'r') do |data_in|
File.open("#{file_num}.txt", 'w') do |data_out|
data_in.each_line do |line|
data_out.puts line
bytes += line.length
if bytes > MAX_BYTES
bytes = 0
file_num += 1
# next file
end
end
end
end
这件作品,但我认为它不优雅。此外,我仍然想知道它是否可以在块上下文中使用File.open来完成。
#!/usr/bin/env ruby
require 'pp'
MAX_BYTES = 5_000_000
file_num = 0
bytes = 0
File.open("test.txt", 'r') do |data_in|
data_out = File.open("#{file_num}.txt", 'w')
data_in.each_line do |line|
data_out = File.open("#{file_num}.txt", 'w') unless data_out.respond_to? :write
data_out.puts line
bytes += line.length
if bytes > MAX_BYTES
bytes = 0
file_num += 1
data_out.close
end
end
data_out.close if data_out.respond_to? :close
end
干杯,
Martin
[更新]编写一个没有任何辅助变量的短版本,并将所有内容放入一个方法中:
def chunker f_in, out_pref, chunksize = 1_073_741_824
File.open(f_in,"r") do |fh_in|
until fh_in.eof?
File.open("#{out_pref}_#{"%05d"%(fh_in.pos/chunksize)}.txt","w") do |fh_out|
fh_out << fh_in.read(chunksize)
end
end
end
end
chunker "inputfile.txt", "output_prefix" (, desired_chunk_size)
您可以使用.read(length)
,而不是循环行,只对EOF
标记和文件光标执行循环。
这样可以确保大块文件永远不会大于您想要的块大小。
另一方面,它从不关心换行(n
)!
区块文件的数字将由当前文件游标位置除以区块大小的整数生成,格式为"%"05d";其产生具有前导零的5位数(00001
)。
这是可能的,因为使用了.read(chunksize)
。在下面的第二个示例中,它无法使用!
更新:换行识别拆分
如果你真的需要n
的完整行,那么使用这个修改后的代码片段:
def chunker f_in, out_pref, chunksize = 1_073_741_824
outfilenum = 1
File.open(f_in,"r") do |fh_in|
until fh_in.eof?
File.open("#{out_pref}_#{outfilenum}.txt","w") do |fh_out|
loop do
line = fh_in.readline
fh_out << line
break if fh_out.size > (chunksize-line.length) || fh_in.eof?
end
end
outfilenum += 1
end
end
end
我不得不引入一个辅助变量line
,因为我想确保块文件大小始终低于chunksize
的限制!如果不进行此扩展检查,文件大小也会超过限制。while
语句只有在行已经写入时才能在下一个迭代步骤中成功检查。(使用.ungetc
或其他复杂计算会使代码更不可读,并且不会比本例更短。)
不幸的是,您必须进行第二次EOF
检查,因为最后一次块迭代将主要产生较小的块。
还需要两个辅助变量:如上所述的line
,需要outfilenum
,因为生成的文件大小大多与精确的chunksize
不匹配。
对于任何大小的文件,即使考虑到启动单独可执行文件的成本,split
也将比从头构建的Ruby代码更快。它也是您不必编写、调试或维护的代码:
system("split -C 1M -d test.txt ''")
选项包括:
-C 1M
在每个区块中放入总计不超过1M的行-d
在输出文件名中使用十进制后缀test.txt
输入文件的名称''
使用空白输出文件前缀
除非你在Windows上,否则这就是你的选择。
不要在infile块中打开outfile,而是打开文件并将其分配给变量。当达到文件大小限制时,请关闭该文件并打开一个新文件。
这段代码实际上很有效,它很简单,并且使用了数组,这使它更快:
#!/usr/bin/env ruby
data = Array.new()
MAX_BYTES = 3500
MAX_LINES = 32
lineNum = 0
file_num = 0
bytes = 0
filename = 'W:/IN/tangoZ.txt_100.TXT'
r = File.exist?(filename)
puts 'File exists =' + r.to_s + ' ' + filename
file=File.open(filename,"r")
line_count = file.readlines.size
file_size = File.size(filename).to_f / 1024000
puts 'Total lines=' + line_count.to_s + ' size=' + file_size.to_s + ' Mb'
puts ' '
file = File.open(filename,"r")
#puts '1 File open read ' + filename
file.each{|line|
bytes += line.length
lineNum += 1
data << line
if bytes > MAX_BYTES then
# if lineNum > MAX_LINES then
bytes = 0
file_num += 1
#puts '_2 File open write ' + file_num.to_s + ' lines ' + lineNum.to_s
File.open("#{file_num}.txt", 'w') {|f| f.write data.join}
data.clear
lineNum = 0
end
}
## write leftovers
file_num += 1
#puts '__3 File open write FINAL' + file_num.to_s + ' lines ' + lineNum.to_s
File.open("#{file_num}.txt", 'w') {|f| f.write data.join}