オクタルダンプの同値省略

linuxだと、odにしてもhdにしても、同じ値が連続していると、途中のダンプ表示が省略できます。しかも省略するのにオプションは要らないので、とても便利です。

takk@deb9:~/tmp$ truncate -s 100 a.bin
takk@deb9:~/tmp$ od a.bin
0000000 000000 000000 000000 000000 000000 000000 000000 000000
*
0000140 000000 000000
0000144
takk@deb9:~/tmp$ hd a.bin
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000064
takk@deb9:~/tmp$

どのような条件で省略されるのでしょうか。
作りやすさから考えると、行を表示する前に、前行と同じかチェックして、*を表示するか、値を表示するか決めているのではないかと思いますが。。。

1行 1Byteにして、確認してみます。

takk@deb9:~/tmp$ od -td1 -Ad -w1 a.bin
0000000    0
*
0000100
takk@deb9:~/tmp$

さきほど作ったa.binは、100Byteのオール0なので、1行 1 Byteだと最初だけ値が表示されました。

次は1行2Byte。

takk@deb9:~/tmp$ od -td1 -Ad -w2 a.bin
0000000    0    0
*
0000100
takk@deb9:~/tmp$

同じく一番最後まで省略されました。

takk@deb9:~/tmp$ od -td1 -Ad -w3 a.bin
0000000    0    0    0
*
0000099    0
0000100
takk@deb9:~/tmp$

3Byte毎だと最後の1Byteが余って、前行と比較した結果不一致なので表示された、ということでしょう。

次は先頭に0以外の値を入れてみます。

takk@deb9:~/tmp$ dd of=a.bin bs=1 count=1 conv=notrunc
a
1+0 レコード入力
1+0 レコード出力
1 byte copied, 1.37189 s, 0.0 kB/s
takk@deb9:~/tmp$

1行 1Byteで確認。

takk@deb9:~/tmp$ od -td1 -Ad -w1 a.bin
0000000   97
0000001    0
*
0000100
takk@deb9:~/tmp$

次は、3Byte目を1Byte目と同じ値にします。

takk@deb9:~/tmp$ dd of=a.bin bs=1 count=1 conv=notrunc seek=2
a
1+0 レコード入力
1+0 レコード出力
1 byte copied, 1.68292 s, 0.0 kB/s
takk@deb9:~/tmp$
takk@deb9:~/tmp$ od -td1 -Ad -w1 a.bin
0000000   97
0000001    0
0000002   97
0000003    0
*
0000100
takk@deb9:~/tmp$

1行1Byteだと97 00、97 00と、同じパターンの繰り返しであっても、省略はされてません。

takk@deb9:~/tmp$ od -td1 -Ad -w2 a.bin
0000000   97    0
*
0000004    0    0
*
0000100
takk@deb9:~/tmp$

1行2Byteの表示になることにより、2行目は1行目と一致するので、省略されました。

やはり、行を表示する時に、前回表示した内容と一致しているか判定してるっぽいですね。
では、odのソースで答え合わせをしてみます。coreutilsです。

odの省略表示するかどうかのオプションは、-vですので、オプション解析の定型文case オプション:で検索します。

takk@deb9:~/src/coreutils-8.26/src$ grep 'case.*v' -A3 od.c
        case '\v':
          s = "\\v";
          break;

--
            case '\v':
              fputs ("\\v", stdout);
              break;

--
        case 'v':
          modern = true;
          abbreviate_duplicate_blocks = false;
          break;
takk@deb9:~/src/coreutils-8.26/src$

-vオプションに該当するのは、一番下にあるcase ‘v’です。このcaseに入った時のフラグであるabbreviate_duplicate_blocksを追いかければ良いですね。
まあ、追いかけるのも面倒いので、やはりgrepです。

takk@deb9:~/src/coreutils-8.26/src$ grep 'abbrevi.*blocks' od.c
static bool abbreviate_duplicate_blocks = true;
  if (abbreviate_duplicate_blocks
          abbreviate_duplicate_blocks = false;
takk@deb9:~/src/coreutils-8.26/src$

ifで判定して使用してる箇所が怪しいので、 前後10行ぐらいを表示してみます。

takk@deb9:~/src/coreutils-8.26/src$ grep 'if.*abbrevi.*blocks' -C10 od.c

static void
write_block (uintmax_t current_offset, size_t n_bytes,
             const char *prev_block, const char *curr_block)
{
  static bool first = true;
  static bool prev_pair_equal = false;

#define EQUAL_BLOCKS(b1, b2) (memcmp (b1, b2, bytes_per_block) == 0)

  if (abbreviate_duplicate_blocks
      && !first && n_bytes == bytes_per_block
      && EQUAL_BLOCKS (prev_block, curr_block))
    {
      if (prev_pair_equal)
        {
          /* The two preceding blocks were equal, and the current
             block is the same as the last one, so print nothing.  */
        }
      else
        {
takk@deb9:~/src/coreutils-8.26/src$

予想通りでした。
前のブロック(prev_block)と、今のブロック(curr_block)を比較してます。
EQUAL_BLOCKSというマクロを直前に定義しているのが面白いです。敢えてマクロを書くというテクニック、私的には大収穫です。

コメント

タイトルとURLをコピーしました