2011-10-21 41 views
5

基本上我想從內存流數據到tar/gz格式(可能有多個文件到tar中,但它不應該接觸HARDDRIVE,只有流!),然後將它們流到其他地方(一個HTTP請求正文在我的情況)。Ruby流tar/gz

任何人都知道現有的庫可以做到這一點? Rails中有什麼嗎?

libarchive-ruby只是一個C包裝,看起來它會非常依賴於平臺(文檔希望您編譯爲安裝步驟?!)。

SOLUTION:

require 'zlib' 
require 'rubygems/package' 

tar = StringIO.new 

Gem::Package::TarWriter.new(tar) { |writer| 
    writer.add_file("a_file.txt", 0644) { |f| 
    (1..1000).each { |i| 
     f.write("some text\n") 
    } 
    } 
    writer.add_file("another_file.txt", 0644) { |f| 
    f.write("some more text\n") 
    } 
} 
tar.seek(0) 

gz = Zlib::GzipWriter.new(File.new('this_is_a_tar_gz.tar.gz', 'wb')) # Make sure you use 'wb' for binary write! 
gz.write(tar.read) 
tar.close 
gz.close 

這就是它!您可以使用任何IO替換GzipWriter中的文件以保持流式傳輸。 Cookies爲dw11wtq!

+0

我還應該指出,這真的是內存密集型 - 它將在轉到gzip流之前用整個tar填滿StringIO。對於大文件更好的解決方案是在流之間創建緩衝區。當我開始執行它時,我將爲此添加代碼... –

+1

另請注意,gz.close也會關閉輸出IO(本例中爲File)。要保持它打開,請使用gz.finish –

回答

6

看看rubygems中的TarWriter類:http://rubygems.rubyforge.org/rubygems-update/Gem/Package/TarWriter.html它只是在IO流上運行,它可能是一個StringIO。

tar = StringIO.new 

Gem::Package::TarWriter.new(tar) do |writer| 
    writer.add_file("hello_world.txt", 0644) { |f| f.write("Hello world!\n") } 
end 

tar.seek(0) 

p tar.read #=> mostly padding, but a tar nonetheless 

它還提供了在tarball中需要目錄佈局時添加目錄的方法。

僅供參考,你可以實現與IO.popen的gzip壓縮,只是通過管道將數據輸入/輸出系統進程:

http://www.ruby-doc.org/core-1.9.2/IO.html#method-c-popen

的gzip壓縮本身將是這個樣子:

gzippped_data = IO.popen("gzip", "w+") do |gzip| 
    gzip.puts "Hello world!" 
    gzip.close_write 
    gzip.read 
end 
# => "\u001F\x8B\b\u0000\xFD\u001D\xA2N\u0000\u0003\xF3H\xCD\xC9\xC9W(\xCF/\xCAIQ\xE4\u0002\u0000A䩲\r\u0000\u0000\u0000" 
+0

是否可以寫入tar/gz函數並從IO流讀取輸出BOTH?我不想觸摸硬盤,所以沒有文件允許! –

+0

另外,它需要獨立於平臺,我寧願不依賴系統調用。我使用的工具需要是我可以打包自己的庫,比如gem或rb文件。這就是爲什麼我遠離libarchive-ruby。 –

+0

再次看,這可能工作。我相信zlib'z Zlib :: GzipWriter可以使用流輸入和輸出,並且TarWriter也可以使用StringIO,正如你所提到的。我會嘗試它,如果它有效,給你餅乾。 –

0

基於OP寫的解決方案,我寫了完整的on-memory tgz存檔功能,我想用於POST到web服務器。

# Create tar gz archive file from files, on the memory. 
    # Parameters: 
    # files: Array of hash with key "filename" and "body" 
    #  Ex: [{"filename": "foo.txt", "body": "This is foo.txt"},...] 
    # 
    # Return:: tar_gz archived image as string 
    def create_tgz_archive_from_files(files) 
    tar = StringIO.new 
    Gem::Package::TarWriter.new(tar){ |tar_writer| 
     files.each{|file| 
     tar_writer.add_file(file['filename'], 0644){|f| 
      f.write(file['body']) 
     } 
     } 
    } 
    tar.rewind 

    gz = StringIO.new('', 'r+b') 
    gz.set_encoding("BINARY") 
    gz_writer = Zlib::GzipWriter.new(gz) 
    gz_writer.write(tar.read) 
    tar.close 
    gz_writer.finish 
    gz.rewind 
    tar_gz_buf = gz.read 
    return tar_gz_buf 
    end