素人なりにシェル芸で1バイトより小さいデータを扱ってみた

前回記事『「第15回ドキッ!grepだらけのシェル芸勉強会」の復習』を書き上げた後、 問題提供元の「上田ブログ」 にて下記の様な記事を見つけました。

日記(シェル芸で1バイトより小さいデータを扱うことを強要されて死ぬ)

「ビットシフトで圧縮とは、さすがシェル芸家元やな……」と思いつつ、

  1. 元データ(の一部)が「ff000102」であると仮定
  2. 本文の条件で表現すると「00011011」になる
  3. 2進数8桁から16進数2桁に変換して「1B」を得る(printfコマンドを使用)

という手法を用いたら、 「元記事と同じ圧縮率(4分の1)でデータの圧縮が出来るのでは?」 とベタなことを考え付いてしまったので早速実行に移してみました。


まず、圧縮するための元データを作ります (policy.txtは元記事のサンプルコードをコピーしたものから作成しました)。

$ echo -en $(cat policy.txt | fold -b2 | sed 's/^\(..\)$/\\x\1/' | tr -d '\n') > policy.dat

サイズは300バイトで内容は以下の通りです。

$ ls -al policy.dat
-rw-r--r-- 1 mitsuhisa mitsuhisa 300  2月 15 10:23 policy.dat
$ xxd -ps policy.dat
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffff01ffff0000ff010001
ffff010001ffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffff01010100010101000000ff01ff00000000ffff01
010101000000ffffffff01010100ffff000000ffffffffffffffffffff00
ffffffffffffffff0000ff000100ffff0000ffff0000ffffffffffffffff
ffffffffffffffffffff00ffff0000ffff0001010000ffff010100010100
ffffff0101ff01010101ff01010101010001010101ffffff000101010101

前述の手法を踏まえつつ、echoコマンドの-eオプションが

バックスラッシュによるエスケープを解釈する

ことと、

$ printf '\\x%02X' $((2#00011011))
\x1B

上記の様に printfコマンドを用いることで以下のワンライナーが出来ましたので実行します。

$ echo -en $(xxd -ps policy.dat | fold -b2 | sed -e 's/^ff$/b00/' -e 's/^00$/b01/' -e 's/^01$/b10/' -e 's/^02$/b11/' | tr -d 'b\n' | fold -b8 | sed '$a\' | while read _2; do printf '\\x%02X' $((2#$_2)); done) > policy.bin

圧縮したファイルpolicy.binのサイズと中身を確認してみます。

$ ls -al policy.bin
-rw-r--r-- 1 mitsuhisa mitsuhisa 75  2月 15 10:42 policy.bin
$ xxd -ps policy.bin
000000000000000000000000000000000000000000000000000000020526
09800000000000000000a9a9521542a9500a905400001000051905050000
000004141a50a69028aa2aa6a806aa

サイズは75バイトで(300バイトだった元データの)4分の1になっています。

そして「圧縮」といえば「解凍」というわけですが、xxdコマンドの-bオプションが

-b | -bits
   ビット (2進数) ダンプ。
   1 オクテットが “1” と “0” の 8 文字で出力されます。
   各行の行頭には 16 進数の行番号が表示されます。
   行末には ascii (または ebcdic) で表した場合の文字が表示されます。
   このモードでは -r、-p、-i は機能しません。

であることを利用して、以下のワンライナーで解凍します。

$ echo -en $(xxd -b policy.bin | sed -e 's/^[^:]\+: //' -e 's/  .*$//' | tr -d ' ' | fold -b2 | sed -e 's/^00$/ff/' -e 's/^01$/00/' -e 's/^10$/01/' -e 's/^11$/02/' | sed 's/^\(..\)$/\\x\1/' | tr -d '\n') > policy.b2d

これもサイズと中身を確認します。

$ ls -al policy.b2d
-rw-r--r-- 1 mitsuhisa mitsuhisa 300  2月 15 10:55 policy.b2d
$ xxd -ps policy.b2d
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffff01ffff0000ff010001
ffff010001ffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffff01010100010101000000ff01ff00000000ffff01
010101000000ffffffff01010100ffff000000ffffffffffffffffffff00
ffffffffffffffff0000ff000100ffff0000ffff0000ffffffffffffffff
ffffffffffffffffffff00ffff0000ffff0001010000ffff010100010100
ffffff0101ff01010101ff01010101010001010101ffffff000101010101

サイズ(300バイト)・中身ともに元データと同じになりました。


……シェル芸人への道はまだまだ遠い。

Written on February 15, 2015