圧縮ファイルを grep する

業務中に圧縮ログを検索したいという状況がたまにあると思います。
そんなとき本記事が参考になれば幸いです。

前提

知識

下記を理解していないと難しい内容かもしれません。

  • シェル(sh)のパイプライン

想定読者

  • 圧縮ファイルが展開できないほど大きいのでそのまま検索したい
  • 圧縮ファイルを展開するのが面倒なのでそのまま検索したい
  • 圧縮ファイル内の検索で余計なソフトをインストールしたくない
  • 圧縮 tar ファイルの中とかも検索したい

モチベーション

弊社は Heroku を使ったプロダクトが多いため、 Papertrail でログを管理する管理することが多いです。

Papertrail のログは、一定期間経つと .tsv.gz ファイルで保存されるのですが、9 ヶ月分のログを調査する必要に迫られました。
ですが、素直にログを展開しようとすると、あっというまに数十 GB を超えてしまい、慌てて gunzip コマンド 1 を terminate (終了)させました。

やむなく圧縮ファイルのまま検索を行おうとしたのが今回のモチベーションです。

圧縮ファイルの検索

1. 素直にツールを使う

有名どころでは ripgrep を利用するのが良いと思います。

インストール方法は割愛します。こちら などをご参考ください。

ripgrep は rg コマンドで呼び出し、-z もしくは --search-zip オプションで圧縮ファイル内の文字列も検索してくれます。

$ rg -z '検索文字列' 圧縮ファイル名

例として fortune コマンド2で作成したサンプルファイル fortunes.txt.gz を利用します3
次のコマンドでサンプルファイルを作成しました。

$ for i in `seq 1 100` ; do fortune ; echo "-----" ; done >> fortunes.txt
$ gzip fortunes.txt # fortunes.txt.gz が作成される

rg コマンドに -z オプションをつけると、圧縮ファイルのまま検索できていることがわかります。

$ rg -z programming fortunes.txt.gz
375:    A programming language that is sort of like Pascal except more like

grep コマンドと同様のオプションも一部サポートしています。

$ rg -z -A 4 programming fortunes.txt.gz # マッチ行後 4 行も表示
375:    A programming language that is sort of like Pascal except more like
376-    assembly except that it isn't very much like either one, or anything
377-    else.  It is either the best language available to the art today, or
378-    it isn't.
379-            -- Ray Simard

$ rg -z -C 4 programming fortunes.txt.gz # マッチ行前後 4 行を表示
371------
372-Life exists for no known purpose.
373------
374-C, n:
375:    A programming language that is sort of like Pascal except more like
376-    assembly except that it isn't very much like either one, or anything
377-    else.  It is either the best language available to the art today, or
378-    it isn't.
379-            -- Ray Simard

2. パイプラインを駆使する

ripgrep を利用しない場合、展開後のデータを(ファイルに書き出さず)そのままパイプに渡すことで検索できます。

# gzip -d で gzip ファイルを展開できる
$ cat fortunes.txt.gz | gzip -d | grep -C 4 programming
-----
Life exists for no known purpose.
-----
C, n:
        A programming language that is sort of like Pascal except more like
        assembly except that it isn't very much like either one, or anything
        else.  It is either the best language available to the art today, or
        it isn't.
                -- Ray Simard

複数ファイルを一度に検索したい場合は、for を利用できます。
以下 Bash での例です。

$ for f in *.txt.gz ; do
    cat "$f" |
    gzip -d |
    grep programming # 検索したい文字列を指定
done
# 結果
        A programming language that is sort of like Pascal except more like

sed でファイル名を先頭につけるとわかりやすくなります。

$ for f in *.txt.gz ; do
    cat "$f" |
    gzip -d |
    grep programming |
    sed -e "s/^/$f /"
done
# 結果
fortunes.txt.gz:        A programming language that is sort of like Pascal except more like

応用:アーカイブファイルの検索

.tar.gz 拡張子の圧縮アーカイブファイルを、一時ファイルなしで検索する場合のケースを考えます。

検証用のファイル fortunes.tar.gz を作成します。

# 1000 回ずつ fortune コマンドの実行結果を書き出したファイルを 5 つ用意
$ for i in `seq 5` ; do for j in `seq 1000` ; do fortune; echo "-----" ; done >> fortunes-${i}.txt ; done
# 少し時間がかかります

# 圧縮アーカイブの作成
$ tar cvzf fortunes.tar.gz *.txt
fortunes-1.txt
fortunes-2.txt
fortunes-3.txt
fortunes-4.txt
fortunes-5.txt

$ rm *.txt
$ ls
fortunes.tar.gz

この場合、検索するために通常は

  1. tar ファイルの展開
  2. ファイルごとの文字列検索

という 2 ステップ必要ですが、これもパイプを利用して一時ファイルなしに行うことができます。

ファイル名等関係なく、ただ検索したい場合は、tar コマンドで -O もしくは --to-stdout オプションをつけて展開すると、ファイルの中身だけが標準出力に出力されます。
これをそのまま grep に渡せばよいでしょう。

$ tar -Ozxf fortunes.tar.gz | head -n 2 # 先頭だけ表示
Many a bum show has been saved by the flag.
                -- George M. Cohan

$ ls
fortunes.tar.gz # ファイルは展開されない

# grep で検索
$ tar -Ozxf fortunes.tar.gz | grep programming
        "After three days without programming, life becomes meaningless."
Real programmers disdain structured programming.  Structured programming is
BASIC is to computer programming as QWERTY is to typing.
4. functional           4. digital              4. programming
programming task.
...

「検索にマッチした文字列がどのファイルに含まれていたか」を表示するには、一工夫が必要です。

まず、tar コマンドは -t オプションで、アーカイブファイルのリストが標準出力に書き出されます。

$ tar -tzf fortunes.tar.gz
fortunes-1.txt
fortunes-2.txt
fortunes-3.txt
fortunes-4.txt
fortunes-5.txt

また、tar コマンドはファイル展開時、ファイル名を引数に与えると、与えられたファイルだけが展開されます

$ tar -Oxzf fortunes.tar.gz fortunes-1.txt | head -n 2
Many a bum show has been saved by the flag.
                -- George M. Cohan

$ tar -Oxzf fortunes.tar.gz fortunes-3.txt | head -n 2
Molecule, n.:
        The ultimate, indivisible unit of matter.  It is distinguished

これらと while 文を組み合わせて、検索文字列にファイル名を付与できます。

$ tar -tzf fortunes.tar.gz |
    while read -r f ; do # $f にアーカイブされたファイル名が順に読み込まれる
        tar -Oxzf fortunes.tar.gz "$f" | # ファイル $f のみ標準出力へ書き出す
            grep programming | # 検索
            sed -e "s/^/$f: /" # 検索文字列の先頭にファイル名を付与
    done

# 結果
fortunes-1.txt:         "After three days without programming, life becomes meaningless."
fortunes-1.txt: Real programmers disdain structured programming.  Structured programming is
fortunes-2.txt: BASIC is to computer programming as QWERTY is to typing.
fortunes-2.txt: 4. functional           4. digital              4. programming
fortunes-2.txt: programming task.
fortunes-2.txt: Algol-60 surely must be regarded as the most important programming language
fortunes-2.txt:                 -- From the programming notebooks of a heretic, 1990.
fortunes-3.txt:         "For the sheer *joy* of programming!" she cries triumphantly.

関数化しておくと汎用的になります。

grep_in_tar() {
    # grep に渡す引数だけ抜き出す
    greparg=()
    while [[ -n $1 ]] ; do
        if [[ $1 =~ \.tar(\.gz)? ]] ; then
            break
        fi
        greparg+=("$1")
        shift
    done

    for tarfile in $@ ; do
        tar -tf "$tarfile" |
        while read -r f ; do
            tar -Oxzf "$tarfile" "$f" |
                grep ${greparg[@]} |
                sed -e "s/^/$f: /"
        done
    done
}

$ grep_in_tar programming fortunes.tar.gz
$ grep_in_tar -A 2 programming fortunes.tar.gz # grep のオプションを利用可能
$ grep_in_tar programming fortunes.tar.gz hoge.tar.gz # 複数の tar ファイルを利用可能

この関数は標準入力から tar データを受け取れないのでもう少しこだわりたいところですが、実務で使う分には十分ではないでしょうか。


以上、圧縮ファイルの grep 方法について紹介しました。
本稿が少しでも日々の業務の助けになりましたら幸いです。


  1. gzip ファイルを展開するコマンドです。↩︎

  2. fortune (UNIX) – Wikipedia↩︎

  3. ランダムな出力のため、同様のコマンドでサンプルを作成しても、同様の結果が得られないと思います。↩︎