gzip は cat で連結できることを最近知った。
なんと gunzip(gzip(A) + gzip(B)) = gunzip(gzip(A+B))
になる。
$ echo "apple" > apple.txt
$ echo "banana" > banana.txt
# 個別に gzip ファイルを作って cat で連結する
$ gzip apple.txt
$ gzip banana.txt
$ cat apple.txt.gz banana.txt.gz > concat.txt.gz
# 展開すると 個別のファイルを連結したもの と同一になる
$ gunzip concat.txt.gz
$ cat concat.txt
apple
banana
なぜ連結できるかというと、gzip ファイルは gzip メンバ(圧縮したデータの集合)の繰り返しの構造になっているため。RFC 1952 GZIP file format specification version 4.3。
これを利用すれば、特定のバイナリを 1000 バイトごとに分割して個別に gzip 圧縮し、
# ls コマンドをコピーして 1000 バイトごとにファイル分割する
$ cp /usr/bin/ls .
$ split -b 1000 ./ls
$ ls
ls xaf xal xar xax xbd xbj xbp xbv xcb xch xcn xct xcz xdf xdl xdr xdx xed
xaa xag xam xas xay xbe xbk xbq xbw xcc xci xco xcu xda xdg xdm xds xdy xee
xab xah xan xat xaz xbf xbl xbr xbx xcd xcj xcp xcv xdb xdh xdn xdt xdz xef
xac xai xao xau xba xbg xbm xbs xby xce xck xcq xcw xdc xdi xdo xdu xea
xad xaj xap xav xbb xbh xbn xbt xbz xcf xcl xcr xcx xdd xdj xdp xdv xeb
xae xak xaq xaw xbc xbi xbo xbu xca xcg xcm xcs xcy xde xdk xdq xdw xec
$ gzip x*
$ ls
ls xai.gz xar.gz xba.gz xbj.gz xbs.gz xcb.gz xck.gz xct.gz xdc.gz xdl.gz xdu.gz xed.gz
xaa.gz xaj.gz xas.gz xbb.gz xbk.gz xbt.gz xcc.gz xcl.gz xcu.gz xdd.gz xdm.gz xdv.gz xee.gz
xab.gz xak.gz xat.gz xbc.gz xbl.gz xbu.gz xcd.gz xcm.gz xcv.gz xde.gz xdn.gz xdw.gz xef.gz
xac.gz xal.gz xau.gz xbd.gz xbm.gz xbv.gz xce.gz xcn.gz xcw.gz xdf.gz xdo.gz xdx.gz
xad.gz xam.gz xav.gz xbe.gz xbn.gz xbw.gz xcf.gz xco.gz xcx.gz xdg.gz xdp.gz xdy.gz
xae.gz xan.gz xaw.gz xbf.gz xbo.gz xbx.gz xcg.gz xcp.gz xcy.gz xdh.gz xdq.gz xdz.gz
xaf.gz xao.gz xax.gz xbg.gz xbp.gz xby.gz xch.gz xcq.gz xcz.gz xdi.gz xdr.gz xea.gz
xag.gz xap.gz xay.gz xbh.gz xbq.gz xbz.gz xci.gz xcr.gz xda.gz xdj.gz xds.gz xeb.gz
xah.gz xaq.gz xaz.gz xbi.gz xbr.gz xca.gz xcj.gz xcs.gz xdb.gz xdk.gz xdt.gz xec.gz
全体を連結したあとに解凍して特定のバイナリを完全に復元することだってできる!
$ cat x* > ls.gz
$ gunzip ls.gz
gzip: ls already exists; do you wish to overwrite (y or n)? y
$ chmod u+x ./ls
$ ./ls
ls xai.gz xar.gz xba.gz xbj.gz xbs.gz xcb.gz xck.gz xct.gz xdc.gz xdl.gz xdu.gz xed.gz
xaa.gz xaj.gz xas.gz xbb.gz xbk.gz xbt.gz xcc.gz xcl.gz xcu.gz xdd.gz xdm.gz xdv.gz xee.gz
xab.gz xak.gz xat.gz xbc.gz xbl.gz xbu.gz xcd.gz xcm.gz xcv.gz xde.gz xdn.gz xdw.gz xef.gz
xac.gz xal.gz xau.gz xbd.gz xbm.gz xbv.gz xce.gz xcn.gz xcw.gz xdf.gz xdo.gz xdx.gz
xad.gz xam.gz xav.gz xbe.gz xbn.gz xbw.gz xcf.gz xco.gz xcx.gz xdg.gz xdp.gz xdy.gz
xae.gz xan.gz xaw.gz xbf.gz xbo.gz xbx.gz xcg.gz xcp.gz xcy.gz xdh.gz xdq.gz xdz.gz
xaf.gz xao.gz xax.gz xbg.gz xbp.gz xby.gz xch.gz xcq.gz xcz.gz xdi.gz xdr.gz xea.gz
xag.gz xap.gz xay.gz xbh.gz xbq.gz xbz.gz xci.gz xcr.gz xda.gz xdj.gz xds.gz xeb.gz
xah.gz xaq.gz xaz.gz xbi.gz xbr.gz xca.gz xcj.gz xcs.gz xdb.gz xdk.gz xdt.gz xec.gz
(いやこの例になんの意味があるかと言ったら意味はないんですが、たまにメール添付サイズの容量の都合で似たことを人為的にやってる人がいたりしますよね。Unix ライクな行動だったんですね。)
ただし注意点としては gzip メンバ(実データではないメタデータ的なもの)の数が増えるので gzip(A) + gzip(B) = gzip(A+B)
ではない。要は全体を cat してから gzip したほうが圧縮効率が上がる。
$ echo "apple" > apple.txt
$ echo "banana" > banana.txt
# 個別に gzip ファイルを作って cat で連結する(72バイト)
$ cat apple.txt.gz banana.txt.gz > gzip+gzip.gz
$ stat --format "%s" gzip+gzip.gz
72
# ファイルを cat で連結してから gzip ファイルを作る(40バイト)
$ cat apple.txt banana.txt > gzip.txt
$ gzip gzip.txt
$ stat --format "%s" gzip.txt.gz
40
なんか古臭い gzip の仕組みを知ったからといってどうなるんだという感はあるが、この gzip の仕組みを利用して tar.gz を Seekable にしようという GitHub - google/crfs: CRFS: Container Registry Filesystem やそれをベースに拡張した stargz-snapshotter/estargz.md at main ・ containerd/stargz-snapshotter ・ GitHub が考案されていて、コンテナ起動の高速化技術である lazy-pulling で利用されている。意外なところで渋い知識が役に立つことがあるんだな(自分は逆に辿ったけど)と思った。