Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
45
Help us understand the problem. What is going on with this article?
@ya-mada

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

More than 5 years have passed since last update.

はじめに

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')
45
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ya-mada
future
ITを武器とした課題解決型のコンサルティングサービスを提供します

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
45
Help us understand the problem. What is going on with this article?