huguma’s blog (仮)

IT技術関連中心の備忘録

Apacheサーバのgzip圧縮最適化: 4. 補足: ファイルのgzip圧縮(初級編)

  1. 確認方法
  2. 設定方法
  3. 事前圧縮による最適化
  4. 補足: ファイルのgzip圧縮(初級編)

はじめに

サイトの事前圧縮処理ではgzipを用いますが、ここではその具体的な方法について補足説明します。できるだけ初級者の方にも分かるような説明を心がけます(Unix系の開発経験者なら読む必要はありません)。

gzipコマンドについて

gzip可逆圧縮アルゴリズムを用いてファイルの圧縮・展開を行うコマンドで、Unix系の環境(LinuxMacなど)には標準でインストールされています。

Windowsgzipを利用するには

Windowsにはgzipは入っていませんが、Windows用のgzipはいくつもあります。私のお勧めはMinGWのMSYSで、これをインストールすればWindowsで(gzipを含む)Unix系基本コマンド一式が使えるようになります。

http://sourceforge.net/projects/mingw/files/

インストール方法は次をご覧ください。msys-baseというのがMSYSで、これを選択するとインストールされます。インストール後のパス設定(例:C:\MinGW\msys\1.0\bin)の追加も忘れずに行って下さい。

http://web.plus-idea.net/2014/06/mingw-install-2014/

使用法(コマンドラインオプション)

ここではサイトの事前圧縮に必要な使用方法だけ説明します。オプションなしでファイル名だけ指定するとそのファイルを消去して拡張子.gzの圧縮ファイルに変換します。次の例はindex.htmlを消去してindex.html.gzを出力します。

$ gzip index.html

すでにターゲットのindex.html.gzが存在する場合はプロンプトを表示して上書きするかどうか聞いてきます。プロンプトを表示させず強制的に上書きするには-fを指定してgzip -f index.htmlとします。

圧縮ファイルを展開して元通りにするには-dを使います。次で圧縮ファイルを消去し、元の非圧縮ファイルを生成します。

$ gzip -d index.html.gz

元ファイルを残して圧縮ファイルを生成する場合は-c(stdoutに出力)を使い、リダイレクトしてファイルを生成します。

$ gzip -c index.html > index.html.gz

圧縮ファイルを残して元ファイルに展開する場合は-d(展開)と-c(stdoutに出力)を組み合わせ、やはり最後はリダイレクトでターゲットファイルを生成します。

$ gzip -dc index.html.gz > index.html

圧縮レベルは-1から-9の間で設定できます(デフォルトは-6)。数値が大きいほど圧縮率が高くなりますが、その分処理に時間がかかります。

最高の-9はデフォルトの約2倍の処理時間を要しますが、圧縮率はあまり変わりません(違いはたかだか1%程度)。特別な理由がなければデフォルト設定で十分です。

ファイルの一括処理

ファイル数が多くなってくると、コマンドを手動でタイプするのが困難になってきます。処理を自動化する方法はいくつもありますが、ここでは次の3通りについて簡単に説明します。

findを使う

(Unixの)findはディレクトリ内を探索し、設定条件に一致したファイルを操作するコマンドです。豊富な機能がありますが使用法はちょっと複雑です(慣れが必要です)。マニュアル(和訳)は次の通りです(しかし読むのは大変でしょう)。

http://linuxjm.osdn.jp/html/GNU_findutils/man1/find.1.html

(Windows用の注意) Windowsのfindコマンドとは全く別です。MSYSにはfindも含まれていますが、普通のパス設定ではWindowsのfind.exeが優先するため動きません。フルパスを指定する(例: C:\MinGW\msys\1.0\bin\find.exe ...)などの対応で利用できます。

まず簡単な使い方の例を示します。./siteディレクトリの中にある全ての.bakファイルを探して削除します(探して表示するだけの場合は-deleteを書かない)。これを知っているだけでも十分便利です。

$ find ./site -name \*.bak -delete

*.bak*の部分をコマンドライン(シェル)が展開しないよう上記の通り\でエスケープするか、またはクォートで囲む必要があります("*.bak")。

次が本題です。-execオプションを使い、条件に一致するファイルに対して任意のコマンドを実行できます。全てのhtmlファイルを検索してgzip圧縮ファイルに変換する例を示します。

$ find ./site -name \*.html -exec gzip {} ";"

元に戻す場合は次の通りです。

$ find ./site -name \*.html.gz -exec gzip -d {} ";"

ポイントをまとめておきます。一番注意する点は最後の;の扱い方です。

  • -execの後;までの引数は実行コマンド名とその引数として認識される
    • コマンドに渡すファイル名の部分には{}を用いる
    • 終了の目印は;(単独の引数として認識させるため手前に空白が必要)
  • bashでは;はコマンド区切り記号なので次の点に注意
    • そのままだとコマンド区切りとして認識される(エラー)
    • \;とエスケープすればOK(bashではポピュラーな書き方)
    • クォートで囲み";"または';'でもOK
  • Windowsコマンドプロンプトの場合は次の通り
    • \;とエスケープするのはエラー
    • 単に;またはクォートで囲めばOK

しかしfindだけでできることには限りがあります。例えば元のファイルを残して圧縮ファイルを生成する場合はリダイレクトを使うため、findの引数一行の中だけで表現するのは困難です。

普通の方法ではできないと思いますが確認していないので「困難」という表現にしました。また仮にできたとしてもシェルスクリプトプログラミング言語を利用した方が理解しやすく管理も容易だと思います。

シェルスクリプトを使う

シェルスクリプトを使えばもっと複雑な処理が可能になります。Bashからfindを起動し、元ファイルを残して.gzファイルを生成する例を示します。-type fは「通常ファイルのみ」の意味です(ディレクトリやシンボリックリンクを除外するための対策)。

#!/bin/bash

# 第一引数($1)のディレクトリ内を探索し、特定の拡張子に対して.gzを生成
find $1 -type f | while read f; do
  # 正規表現マッチして拡張子を判定
  if [[ $f =~ \.(html|js|css|txt)$ ]]; then
    echo $f '=>' $f.gz
    gzip -c $f > $f.gz
  fi
done

スクリプト言語を使う(Rubyの場合)

高級スクリプト言語を使えばさらに複雑な処理を行えます。ここではRubyの場合を説明しますが、Rubyを使うことには次のような利点があります(あくまで個人的見解)。

  • シェルスクリプトと比較して文法が美しく洗練されている
  • 書き方を統一できる(シェルは方言が沢山あるがRubyは基本的にない)
  • 環境依存性をRubyが吸収してくれる(特にWindowsでも同じコードで動く)

PerlPythonなど別の言語が得意な人はそれに置き換えて下さい。得意言語を何か一つ持っているのは大きな武器です。

それでは実際の例を示します。Rubyの標準ライブラリにはfindモジュールがあり、findコマンドと同じ手順でディレクトリ探索を行うことができます(これを使えばfindコマンドは不要です)。

http://docs.ruby-lang.org/ja/2.2.0/library/find.html

Rubyで可能な機能は全て組み込むことができますが、ここではファイルの更新チェック機能を付けます。.gzファイルが存在しない(新規ファイル)か、または元ファイルより古い(ファイルが更新された)場合だけgzipを実行します。

require 'find'

def scan_and_update_gz(dir)
  Find.find dir do |f|
    # 通常ファイルか確認後、拡張子をマッチして特定ファイルタイプを除外
    #                           vvvvvvvvvv <== (例)この部分は要調整
    if File.file?(f) && !(/\.(?:gz|jpg|mp3)$/ =~ f)
      gz = "#{f}.gz"
      # .gzがまだ存在しないか、または元ファイルより古い場合だけ生成
      if !File.exist?(gz) || File.stat(gz).mtime < File.stat(f).mtime
        # puts "#{f} => #{gz}"      # (表示の例) 必要な場合はコメントを外す
        `gzip -c #{f} > #{gz}`     # gzipを実行
      end
    end
  end
end

# 使用例
scan_and_update_gz './site'

この程度までの機能ならシェルスクリプトでも組めますが、(少なくとも私の場合は)Rubyの方がはるかに楽に書けます。またWindowsで同じコードがそのまま使えるのも大きなメリットです。

レンタルサーバを使う場合はさらに改良して、探索時に.gzを更新(生成)したファイルを記憶しておき、更新されたファイルだけftpコマンドで自動アップロードする事もできます。

これは天気データマップの毎日のメインテナンスで実際に行っていますが、非公開部分を含んでいるため割愛します(ご了承下さい)。