ディスク容量節約：tar.bz2やtar.gzをtar.xzに変更するスクリプト&解説

あらまし
=======

先日、Jenkins で保存していたビルド成果物がディスク領域を圧迫してしまい、Jenkinsが止まってしまいました。
早急にディスク領域を空けないとプロジェクトに支障をきたしてしまいます。

成果物を`foo.tar.bz2`のように、tar+bzip2形式で保存したので、これをより圧縮率の高いtar+xz形式に変換することで、ディスク領域を空けることができました。

本題
====

Linuxカーネルのソースコードを使って、実際にやってみましょう。

.tar.bz2の場合
-------------

recompress-bz2.sh に tar+bzip2形式のファイルを食わせればOKです。

```bash:recompress-bz2.sh
#!/bin/bash -ue

BZ2=$1
XZ=${1%.bz2}.xz

trap "echo $0 trapped.; rm -f $XZ" 1 2 3 15

if bzcat $BZ2 | xz -v > $XZ
then
    trap "echo $0 trapped." 1 2 3 15
    rm -f $BZ2
else
    rm -f $XZ
fi
```

```bash:.tar.bz2の場合
$ wget https://www.kernel.org/pub/linux/kernel/v3.0/linux-3.12.tar.bz2
$ ls -lh linux-3.12.tar.bz2
-rw-r--r-- 1 hogefoo hogefoo 87M 11月  4 08:47 linux-3.12.tar.bz2
$ sh recompress-bz2.sh linux-3.12.tar.bz2
  100 %        75.2 MiB / 518.8 MiB = 0.145   2.0 MiB/s       4:16             
$ ls -lh linux-3.12.tar.*
-rw-r--r-- 1 hogefoo hogefoo 76M  3月 14 01:00 linux-3.12.tar.xz
```

もとの`.tar.bz2`ファイルは消え、`.tar.xz`ファイルができています。
容量はざっと **11MB減りました** 。

.tar.gzの場合
------------

.tar.bz2の場合とあんまり変わりません。

```bash:recompress-gz.sh
#!/bin/bash -ue

GZ=$1
XZ=${1%.gz}.xz

trap "echo $0 trapped.; rm -f $XZ" 1 2 3 15

if zcat $GZ | xz -v > $XZ
then
    trap "echo $0 trapped." 1 2 3 15
    rm -f $GZ
else
    rm -f $XZ
fi
```

```bash:tar.gzの場合
$ wget https://www.kernel.org/pub/linux/kernel/v3.0/linux-3.12.tar.gz
$ ls -lh linux-3.12.tar.gz
-rw-r--r-- 1 hogefoo hogefoo 110M 11月  4 08:47 linux-3.12.tar.gz
$ sh recompress-gz.sh linux-3.12.tar.gz
  100 %        75.2 MiB / 518.8 MiB = 0.145   2.0 MiB/s       4:13             
$ ls -lh linux-3.12.tar.*
-rw-r--r-- 1 hogefoo hogefoo 76M  3月 14 01:17 linux-3.12.tar.xz
```

**こちらは34MB減っています** 。

解説
====

tar形式
-------

そもそも、`foo.tar.bz2`というのは、`foo.tar`というtar形式のファイルをbzip2で圧縮したファイルです。
**tar形式に圧縮機能はなく、コンテナとしてファイルとそのメタ情報を記録する機能しかありません**。

なのでbzip2やgzipの圧縮から解いて、ただのtar形式にしてからxzで圧縮すれば、tar.xzの出来上がりです。

tar.bz2やtar.gzからtarへ
-----------------------

`bzcat`や`zcat`を用いると、 **簡単に.tar.bz2 や.tar.gzをtar形式として標準出力に流す** ことができます。

```bash
$ bzcat foo.tar.bz2 > foo.tar
$ zcat bar.tar.gz > bar.tar
```

tarからtar.xzへ
--------------

`xz -`で、 **標準入力をxzで圧縮して標準出力に流すことができます** 。

```bash
$ cat foo.tar | xz - > foo.tar.xz
```

`recompress-bz2.sh`や`recompress-gz.sh`では、`-v`を付けて、進捗を表示させています。

tar.bz2やtar.gz から、tar.xzへ
-----------------------------

標準出力へtar形式を流す方法と、標準入力をxzで圧縮する方法がわかりました。
あとはくっつけるだけです。

```bash
$ bzcat foo.tar.bz2 | xz - > foo.tar.xz
$ zcat bar.tar.gz | xz - > bar.tar.xz
```

おしまい。

蛇足
====

拡張子
-----

`Bash`の機能を用いて、拡張子の変換を行ってます。
POSIX Shellでできるかどうかは調べてません。

**追記** コメント欄より

> POSIX シェルでも出来ます!

とのことです。THX! @magicant 

```bash
GZ=$1
XZ=${1%.gz}.xz
```
`${1%.gz}`は`$1`の末尾から`.gz`を削った結果を返します。



Ctrl-Cの対応
------------

`trap`を用いて、Ctrl-Cで止められた時の処理をしています。

```bash:Linuxの場合
trap "echo $0 trapped.; rm -f $XZ" 1 2 3 15
```
または

```bash:他OSではシグナルの番号が違うことがあるので名前で指定
trap "echo $0 trapped.; rm -f $XZ" HUP INT QUIT TERM
```

第一引数はシグナルを受けたときに実行する処理、
第二引数はシグナルを示しています。

ここでシグナルは番号、名前の両方が使えますが、
OSによって番号がは異なることがあるため、名前を用いた方が移植性が高いようです。
コメント欄参照。 THX! @magicant 

```bash
$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	
```

一般的に、`1 2 3 15`をおさえておけばよいそうです。

また、再圧縮が終わって`rm -rf $GZ`する前に`trap "echo $0 trapped." 1 2 3 15`として、削除処理上書きして無効にすることで、せっかく出来たtar.xzファイルを削除してしまわないようにしています。

他の方法
========

`tar xvzf`なり`tar xvjf`なりで解凍してから`tar xvJf`で圧縮することもできますが、
ディスク領域と時間の無駄なのであんまりオススメできません。

参考
====

`trap`については、以下を参考にしました。
[シグナルと trap コマンド - UNIX & Linux コマンド・シェルスクリプト リファレンス](http://shellscript.sunone.me/signal_and_trap.html シグナルと trap コマンド - UNIX & Linux コマンド・シェルスクリプト リファレンス)

`#!/bin/bash -ue`については、 @youcune の以下の記事を参考にしました
[シェルスクリプトを書くときはset -euしておく](http://qiita.com/youcune/items/fcfb4ad3d7c1edf9dc96 シェルスクリプトを書くときはset -euしておく)

`bash`, `bzcat`, `zcat`, `xz`については、`man bash`, `man bzip2`, `man gzip`, `man xz`を参考にしました。

おことわり
=========

異常処理とか適当なので、データ消失等の事故については自己責任でおねがいします。
もし危ないところに気づいたら編集リクエストなりコメントなりで御指摘いただけると幸いです。

また、語の統一がかなり適当なので、あとで編集するかもです。
大意は変わらないはずです。

あしからず。