あえてbc(1)した話

たぶんまだまだ初級な小ネタ。

こんな感じに16進数(プレフィックスなし)のデータが並んでいるテキストファイルtest.datがあって:

00000000
00000001
0000000F
00000010
7FFFFFFF
80000000
FFFFFFFE
FFFFFFFF

これを元データ(32bit符号なし整数)としてちょっとした分析をしたかった(本来のデータは、もっと件数が多く、値もバラエティに富んでいる。ただし数値としてソート済みなのは同じ)。

したかったのだが、awk(1)とか、デフォルトで16進数を扱えなかった。恥かしながら、初めて気づいた……。

そこでbc(1)を使って10進数に変換してみた。

$ echo ibase = 16 | cat - test.dat
ibase = 16
00000000
00000001
0000000F
00000010
7FFFFFFF
80000000
FFFFFFFE
FFFFFFFF
$ echo ibase = 16 | cat - test.dat | bc
0
1
15
16
2147483647
2147483648
4294967294
4294967295
$ echo ibase = 16 | cat - test.dat | bc >test_dec.dat
$ _

これで、他のツールで扱いやすくなった。

bc(1)を使う手法は、テキスト処理で「力任せな計算式」を組み立ててしまえばよい、という点で、少々興味深い。

$ # 取り扱うデータはこんな感じ。
$ cat test_dec.dat
0
1
15
16
2147483647
2147483648
4294967294
4294967295
$ # 各データについて、2で割った値を求める(小数第二位まで表示)
$ sed '1s/^/scale = 2; /;s!$! / 2!' test_dec.dat
scale = 2; 0 / 2
1 / 2
15 / 2
16 / 2
2147483647 / 2
2147483648 / 2
4294967294 / 2
4294967295 / 2
$ sed '1s/^/scale = 2; /;s!$! / 2!' test_dec.dat | bc
0
.50
7.50
8.00
1073741823.50
1073741824.00
2147483647.00
2147483647.50
$ # 最初のデータを捨てる。
$ # 3番目以降のデータについて、2番目のデータとの差分を求める。
$ sed '1d;2s/^/n = /;3,$s/$/ - n/' test_dec.dat
n = 1
15 - n
16 - n
2147483647 - n
2147483648 - n
4294967294 - n
4294967295 - n
$ sed '1d;2s/^/n = /;3,$s/$/ - n/' test_dec.dat | bc
14
15
2147483646
2147483647
4294967293
4294967294
$ # 1つ前のデータとの差分を求める。
$ sed '1s/^/n = /;2,$s/^.*$/& - n; n = &/' test_dec.dat
n = 0
1 - n; n = 1
15 - n; n = 15
16 - n; n = 16
2147483647 - n; n = 2147483647
2147483648 - n; n = 2147483648
4294967294 - n; n = 4294967294
4294967295 - n; n = 4294967295
$ sed '1s/^/n = /;2,$s/^.*$/& - n; n = &/' test_dec.dat | bc
1
14
1
2147483631
1
2147483646
1
$ _

まあ、不正なデータが入力されない(処理するデータはエラーチェック済みで、不正値は取り除かれている)という前提があるからこそ可能な手法ではある。本質的にはサーバサイドのXSSと同じ危険性があるからなあ。