LoginSignup
65
47

More than 5 years have passed since last update.

RubyでZipを使ってやる!サンプル集

Posted at

はじめに

rubyでzipを使おうとするとziprubyとrubyzipがググれると思います。
サンプルもたくさん出てきます。
しかし「やりたいのこれじゃないんだよ!」と皆さん思いませんでしたか?
ということで、rubyzipのSource読みつつ痒いところに手が届く事を目指して苦労した部分を纏めてみました。

目次

  • rubyzipの理由
  • 設定
  • 圧縮
  • 解凍
  • パスワード付きZIPの扱い(業務アプリなら必須だよね!
  • zipにファイル追加(業務アプリだとやりたいじゃん!
  • 文字化け対策

ziprubyではなくrubyzipの理由

ziprubyの更新が止まっているから。。。

設定

gemにrubyzipを指定

gem 'rubyzip', '1.1.7'

zipをrequire する

require 'zip'

圧縮

  • 方式1:Zip::OutputStreamを使う
    def compress(path, zippath)
      # Stream開く
      buffer = Zip::OutputStream.write_buffer(::StringIO.new('')) do |out|
        out.put_next_entry(File.basename(path))
        # ワンラインで書けたけど、ファイル名が長い?とエラーになるのでStreamを一旦変数に受ける・・・謎
        file_buf = File.open(path) { |e| e.read }
        out.write file_buf
      end
      # Stream書き出す
      File.open(zippath) { |f| f.write(buffer.string) }
      zippath
    end
  • 方式2:Zip::File.open(${ファイル名}, Zip::File::CREATE)を使う

こっちのほうが実装は楽だがパスワードとかは使えない

  # Zip圧縮
  def compress(path, zippath)
    File.unlink zippath if File.file?(zippath)
    Zip::File.open(zippath, Zip::File::CREATE) do |z_fp|
      z_fp.add(File.basename(path), path)
    end
    outpath
  end

解凍

  # Zip解凍
  def uncompress(path, outpath)
    entrys = []
    Dir.mkdir(outpath) unless Dir.exist?(outpath)
    # 2つ目の引数はoffset
    Zip::InputStream.open(path, 0) do |input|
      # get_next_entryするとinputのoffset(ポインタ)が動く
      while (entry = input.get_next_entry)
        # 書き出し先を作る
        save_path = File.join(outpath, entry.name)
        File.open(save_path) do |wf|
          # get_next_entryでポインタが動いているので、毎回input.readでOK
          wf.puts(input.read)
        end
        entrys << save_path
      end
    end
    # 解凍されたファイルたちを返却する
    entrys
  end

パスワード付きZIPの扱い

圧縮

    def compress(path, zippath, password )
      # パスワードのオブジェクト作る
      encrypter = password.present? ? Zip::TraditionalEncrypter.new(password) : nil

      # Stream開く&パスワードを渡す
      # Zip::File.openはTraditionalEncrypterを受けるIFが(多分)ないので、使えない
      buffer = Zip::OutputStream.write_buffer(::StringIO.new(''), encrypter) do |out|
        out.put_next_entry(File.basename(path))
        # ワンラインで書けたけど、ファイル名が長い?とエラーになるのでStreamを一旦変数に受ける・・・謎&Windowsだから?
        file_buf = File.open(path) { |e| e.read }
        out.write file_buf
      end
      # Stream書き出す
      File.open(zippath) { |f| f.write(buffer.string) }
      zippath
    end

解凍

  # Zip解凍
  def uncompress(path, outpath, password )
    entrys = []
    Dir.mkdir(outpath) unless Dir.exist?(outpath)
    # パスワードのオブジェクト作る
    decrypter = password.present? ? Zip::TraditionalDecrypter.new(password) : nil

    Zip::InputStream.open(path, 0, decrypter ) do |input|
      # get_next_entryするとinputのoffset(ポインタ)が動く
      while (entry = input.get_next_entry)
        # 書き出し先を作る
        save_path = File.join(outpath, entry.name)
        File.open(save_path) do |wf|
          # get_next_entryでポインタが動いているので、毎回input.readでOK
          wf.puts(input.read)
        end
        entrys << save_path
      end
    end
    # 解凍されたファイルたちを返却する
    entrys
  end

zipにファイル追加

    # Zip圧縮(ファイル追加)
    def modify(path, zippath, password)
      # 追加するファイルの名前
      addfile_name = File.basename(path)
      # パスワードのオブジェクト作る
      decrypter = nil
      encrypter = nil
      if password.present?
        decrypter = Zip::TraditionalDecrypter.new(password)
        encrypter = Zip::TraditionalEncrypter.new(password)
      end

      entrys = []
      # 出力するZip用のStreamを開く
      buffer = Zip::OutputStream.write_buffer(::StringIO.new(''), encrypter) do |out|
        # ファイルを追加したいZipのStreamを開く
        Zip::InputStream.open(zippath, 0, decrypter) do |input|
          # inputの内容をoutにコピー
          while (entry = input.get_next_entry)
            out.put_next_entry(entry.name)
            file_buf = input.read
            out.write file_buf
            entrys << entry.name
          end
        end
        # inputのentryに追加するファイル名がなければ追加
        unless entrys.include?(addfile_name)
          out.put_next_entry(addfile_name)
          file_buf = File.open(path, 'rb') { |e| e.read }
          out.write file_buf
        end
      end
      File.open(zippath, 'wb') { |f| f.write(buffer.string) }
      zippath
    end

文字化け対策

基本的にバイナリモード使ってください

File.open(path, 'rb'
File.open(zippath, 'wb')

これでもダメならエンコード指定してください

File.open(path, 'rb:utf-8'
File.open(zippath, 'wb:utf-8')
65
47
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
65
47