■ Ruby で hex dump

バイナリのファイルなどを扱いたい場合や、データの形式を確認するときなどは、dump プログラムを使用します。 od(1) や xd(1) (Linux にはないの? od -x みたい) と呼ばれるものです。 ただ、私は od(1) が出力するような形式にはあまりなじみがありません。 Linux の JE などでは jhd が含まれています。 形式としてはこちらの方が気に入っています(懐かしい感じです)。

00000000  23 21 20 2F 75 73 72 2F  6C 6F 63 61 6C 2F 62 69  #! /usr/local/bi
00000010  6E 2F 72 75 62 79 0A 23  20 2F 68 6F 6D 65 2F 74  n/ruby.# /home/t
00000020  65 74 73 75 2F 73 72 63  2F 72 75 62 79 2F 68 64  etsu/src/ruby/hd
00000030  2E 72 62 0A 23 20 43 72  65 61 74 65 64 3A 20 46  .rb.# Created: F
00000040  65 62 72 75 61 72 79 20  32 32 2C 31 39 39 38 20  ebruary 22,1998 
jhd を使用すれば、漢字コードまで dump の横のエリアに表示できてしまうのでとても便利です。

ここでは、次のような出力のものを作りました。

00000080  75627920 82c52068 65782064 756d700d  uby で hex dump.
00000090  0a0d0a83 6f834383 69838a82 cc837483  ...バイナリのフ.
000000a0  40834383 8b82c882 c782f088 b582a282  ァイルなどを扱い.
000000b0  bd82a28f ea8d8782 e2814183 66815b83  たい場合や、デー.
このコマンドは、私にとってとても利用頻度が高いものです。 というのは、ファイルの形式などを確認するときによく使用するからです。

文字コードを dump するプログラムをつくるということで考えると、漢字の扱いが少々ややこしいです。 漢字を表示する場合、「漢字文字コード」の泣き別れ? が起きないようにすることが必要なためです。

■ 使用方法

オプション説明
-S入力の漢字コードを SJIS として解釈します(nkf と同じ)
-E入力の漢字コードを EUC として解釈します(nkf と同じ)
-s出力の漢字コードを SJIS とします(nkf と同じ)
-e出力の漢字コードを EUC とします(デフォルト)(nkf と同じ)
-(10 進数)オフセット指定 10 進数
-0(8 進数)オフセット指定 8 進数
-0x(16 進数)オフセット指定 16 進数
オプション -s, -e, -S, -E を指定しない場合は、漢字の出力は行われません。

使い方は、いたって簡単です。

次の例では、Windows95 のファイルを、入力漢字コード SJIS、出力漢字コード EUC としてファイルの内容をダンプします。

$ hd.rb -S /mnt/dos/Windows/wininit.exe |less

■ ソースコード

Web ブラウザでの表示上は Ruby のスクリプトですが、HTML の特殊文字をエスケープしています。 テキストとしてセーブしてご利用ください。

#! /usr/local/bin/ruby -Kn
# /home/tetsu/src/ruby/toolbox/hd.rb
# Created: February 22,1998 Sunday 18:39:06
# Author: tetsu(WATANABE Tetsuya)
RCS_ID = %q$Id: hd.rb,v 2.4 2001/08/14 18:23:13 tetsu Exp $
# usage: hd.rb -sE -0100 /vmlinuz | less

def usage
  STDERR.print <<E
usage: #$0
     [-seSE]
     [-(num|0xhex|0oct)]
     [files ...]

 -[0-9]+          offset decimal
 -0[0-7]+                octal
 -0x[0-9a-fA-F]+         hex

 -S    sjis/input code
 -E    euc /input code
 -s    sjis/output code
 -e    euc /output code (default)

#{RCS_ID}

E
  exit 1
end

require 'nkf'

class String
  def bin_to_str
    return self.tr("\177-\377", '.') unless $kanji
    w = ($kanji_chip + self).gsub($kanji_re) {
      NKF.nkf($nkf_opt_in + 'j', $1)
    }.gsub($kana_re) {
      NKF.nkf($nkf_opt_in + 'j', $1)
    }

    w[0,1] = '' if $kanji_chip == w[0,1]
    $kanji_chip = if $kanji_chip_re =~ w[-1,1] then w[-1,1] else '' end
    NKF.nkf($nkf_opt_out + 'J', w.tr("\177-\377", '.'))
  end
end

def hd(f, offset)
  if offset > 0
    begin
      f.pos = offset
    rescue
      offset = 0
    end
  end

  l_unpackformat = 'N*'
  unpackformat = 'N'
  l_packformat = 'N*'
  packformat = 'N'
  dumpformat = '%08x  %08x %08x %08x %08x  '
  dumplastformat = '%8.8x'

  back = ''
  p_flag = true

  while $_ = f.read(16)
    break if $_.length != 16

    if back == $_
      print "*\n" if p_flag
      p_flag = false
      offset += 16
      next
    elsif p_flag == false
      p_flag = true
    end

    back = $_

    bin = $_.unpack(l_unpackformat)
    printf(dumpformat, offset, *bin)
    print $_.tr("\000-\037\177", '.').bin_to_str, "\n"
    offset += 16
  end

  unless $_.nil?
    n = $_.length / 4
    left_byte = $_.length % 4
    bin = $_.unpack(unpackformat + n.to_s + 'a' + left_byte.to_s)

    n.times {|i| bin[i] = format(dumplastformat, bin[i])}

    if left_byte > 0
      w = bin[-1].unpack('C*')
      foo = ''
      w.each_index {|i| foo.concat(format('%2.2x', w[i]))}
      bin[-1] = foo + '  ' * (4 - left_byte)
    end

    (16 / 4 - bin.size).times {bin.push(' ')}

    printf(dumpformat.gsub('x', 's'), format('%08x', offset), *bin)
    print $_.tr("\000-\037\177", '.').bin_to_str, "\n"
  end
end

$kanji = false
$kanji_chip = ''
$nkf_opt_in = '-E'
$nkf_opt_out = '-e'

offset = 0

while $_ = ARGV[0] and ~/^-/
  case ARGV.shift
  when /^-0([0-7]+)$/
    offset = $1.oct
  when /^-0x([\da-fA-F]+)$/
    offset = $1.hex
  when /^-(\d+)$/
    offset = $1.to_i
  when /^-[se]+$/i
    $kanji = true
    $nkf_opt_in = '-' + $1 if ~/([SE])/
    $nkf_opt_out = '-' + $1 if ~/([se])/
  else
    usage
  end
end

if $nkf_opt_in == '-E'
  # - f4ff (less)
  $kanji_re = /((?:[\241-\364][\241-\376])+)/n
  $kana_re = /((?:\216[\241-\337])+)/n
  $kanji_chip_re = /^[\216\241-\376]$/n
else
  # 0x81-0x9f 0xe0-0xfc/0x40-0x7e 0x80-0xfc
  # 0x40-0x7e 0x80-0xa0 0xe0-0xfc
  # - eafc (less)
  $kanji_re = /((?:[\201-\237\340-\352][\100-\176\200-\374])+)/n
  # kconv/nkf 0xa0-0xdf
  $kana_re = /([\241-\337]+)/n
  $kanji_chip_re = /^[\201-\237\340-\357]$/n
end

if ARGV.length == 0
  hd(STDIN, offset)
else
  while file = ARGV.shift
    f = File.open(file)
    hd(f, offset)
    f.close
  end
end

exit

■ 履歴

2.4 2001/8/14

$_ のマッチで ~/reg_exp/ 形式への変更に対応


渡辺哲也(WATANABE Tetsuya): Tetsuya.WATANABE atmark nifty.com