Rubyでバイナリファイルを扱って苦労した記録

2011 年 3 月 28 日 by 山平

そもそもRubyのような高級言語で行うことではないのですが、バイナリファイルにランダムアクセスして読み書き両方を行いたいのにやたら苦労してしまったので記録します。

以下のような処理を行うプログラムを書こうとしています。

  1. ファイルを開く
  2. ファイルの先頭に移動
  3. その場で1バイト読込み
  4. 条件=trueならその場に書込み
  5. 次のバイトに移動
  6. ファイルの末尾まで3-5を繰り返し

ポインタが定まらない

IO#seek(1,IO::SEEK_CUR)で次々と後ろのバイトに進んでいく予定がどうも思い通りにならず、
どうもIO#readで読み込むとポインタが勝手に動いている?ように見えました。
しかたがないのでここは読書きの直前にIO#posで絶対位置を指定することで回避しました。

1バイト数値として認識させたい

ファイルがテキスト・バイナリに関わらず、読み出した1バイトは0~255の数値で扱いたいのですが、
IO#readは文字列として返すのが仕様のようです。
文字に割り当てられていない値はなぜか”\377″のように8進数で表記されています。。。
これを数値で表すにはどうすればよいか、調べてみました。

リテラル表記

「?」+文字でその文字コードが得られます。

irb(main):003:0> ?a
=> 97

が、あくまでリテラルなので、読み出した1バイトをevalで評価してやる必要があります。

irb(main):004:0> eval(“#{‘?’+’a’}”)
=> 97

文字でないものは適切にエスケープしてやればコードが得られるようです。

irb(main):008:0> eval(“#{‘?’+’\\377’}”)
=> 255

文字配列

Rubyの文字列はバイト配列として扱うことができ、その値は文字コードが得られます。

irb(main):009:0> “a”[0]
=> 97
irb(main):010:0> “\377″[0]
=> 255

evalを使うよりシンプルにかけるのでこちらを採用します。

サンプルプログラム

ここまでの調査で読み出しプログラムがそれらしく動き始めました。

[Ruby]
#!/usr/bin/ruby

filename = ARGV[0]
size = File.size?(filename)
File.open(filename,”r+b”){|file|
size.times do |fp|
file.pos = fp
c = file.read(1)
raise RuntimeError, ‘overflow.’ if !c
puts “#{c}(#{c[0]})”
end
}
[/Ruby]

なぜかテキストファイルは末尾に改行コードがなくても改行コードがあるように振る舞っています。

ファイルサイズをよく見ると、文字数+1バイトになっているので、これはファイルシステム側に原因がありそうです。
今回はここには深入りしませんでした。

今回はここまでにします。
以上です。

タグ:

TrackBack