Ruby: как разбить файл на несколько файлов заданного размера - PullRequest
7 голосов
/ 27 мая 2011

Я хочу разбить текстовый файл на несколько файлов, где каждый файл содержит не более 5 МБ.Я знаю, что для этого есть инструменты, но мне это нужно для проекта, и я хочу сделать это в 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

Cheers,

Martin

Ответы [ 4 ]

16 голосов
/ 27 мая 2011

[Обновлено] Написал короткую версию без каких-либо вспомогательных переменных и поместил все в метод:

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|
        line = ""
        while fh_out.size <= (chunksize-line.length) && !fh_in.eof?
          line = fh_in.readline
          fh_out << line
        end
      end
      outfilenum += 1
    end
  end
end

Мне пришлось ввести вспомогательную переменную line, потому что я хочу убедиться, что размер файла всегда будет на ниже chunksize предел!Если вы не выполните эту расширенную проверку, вы также получите размеры файлов, превышающие лимит.Оператор while успешно проверяет только следующий шаг итерации, когда строка уже записана.(Работа с .ungetc или другими сложными вычислениями сделает код более нечитаемым и не короче, чем в этом примере.)

К сожалению, вам понадобится вторая проверка EOF, потому что последняя итерация фрагмента в большинстве случаев приведет кв меньшем куске.

Также необходимы две вспомогательные переменные: line описан выше, outfilenum необходим, потому что результирующие размеры файлов в основном не соответствуют точным chunksize.

11 голосов
/ 28 мая 2011

Для файлов любого размера split будет быстрее, чем код Ruby, созданный с нуля, даже с учетом стоимости запуска отдельного исполняемого файла. Это также код, который вам не нужно писать, отлаживать или поддерживать:

system("split -C 1M -d test.txt ''")

Варианты:

  • -C 1M Положить строки общим объемом не более 1М в каждом чанке
  • -d Использовать десятичные суффиксы в выходных именах файлов
  • test.txt Имя входного файла
  • '' Использовать пустой префикс выходного файла

Если вы не используете Windows, это путь.

0 голосов
/ 15 января 2016

Этот код на самом деле работает, он прост и использует массив, что делает его быстрее:

#!/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}
0 голосов
/ 27 мая 2011

Вместо открытия вашего выходного файла внутри блока infile, откройте файл и назначьте его переменной. Когда вы достигнете предела размера файла, закройте файл и откройте новый.

...