ファイルを256進数値としてインクリメントするコマンド

2011 年 5 月 16 日 by 山平

前回、バイナリファイルを読み書きする方法について調査しました。
今回作成したこのコマンドがその目的になります。

動作の内容

  1. ファイルをオープンする
  2. ポインタfpに0をセット
  3. ループ開始
  4. fpの1バイトを読み込み、10進数値cに変換
  5. c<255ならc=c+1、桁フラグはfalse
  6. 4.ではない(=255)なら、c=0、桁フラグはtrue
  7. fpにcを書き込み
  8. fp=fp+1
  9. fp>=ファイルサイズならループを抜ける
  10. 桁フラグがfalseならループを抜ける
  11. 次のループへ
  12. ファイルクローズする
  13. 桁フラグがtrueなら1を、falseなら0を出力

要は、ファイル全体を256進数の数値と見なしてインクリメントするものを作ります。
今回はこのプログラムを解説します。

プログラムの解説

では順番に見ていきましょう。

1.引数のチェック

[Ruby]
if !ARGV then
puts “usage: ruby binc.rb filename”
exit
end
filename = ARGV[0]
raise ArgumentError, ‘file not found.’ if !File.exist?(filename)
size = File.size?(filename)
raise ArgumentError, ‘may be empty file.’ if !size
[/Ruby]

必ずインクリメントするファイル名を一つ取るので、最低限下記の内容をチェックしています。

  • 引数があること
  • 存在するファイルパスであること
  • サイズが0より大きいこと

2.初期化

[Ruby]
fp, a = 0, 1
[/Ruby]

ポインタと桁フラグの初期値を設定します。
1回目のループ終了条件を回避する意味合いがあります。

3.ファイルオープン

[Ruby]
File.open(filename,”r+b”){|file|

}
[/Ruby]

バイナリモードでファイルを開きます。

4.ループの開始と終了条件

[Ruby]
loop do
break if fp >= size
break if a == 0

end
[/Ruby]

ポインタがファイルサイズより大きくなると、これ以上桁が上がれないので終了します。
このとき、桁フラグは1のまま終了します(桁あふれ)。
もう次の桁をインクリメントしなくて良いならば終了します。
このとき、桁フラグは0で終了します。

4.値の読み取り

[Ruby]
file.pos = fp
c = file.read(1)[0]
[/Ruby]

前回の調査から、読み取り前に位置を指定します。
読み込んだ後は文字列の1文字目という扱いで数値として取得します。

5.インクリメント

[Ruby]
w = c
if c == 255 then
w, a = 0, 1
else
w, a = c+1, 0
end
[/Ruby]

桁が上がる場合と上がらない場合の分岐はif文で判定しています。
もうちょっとスマートにかけると良いのですが。

6.値の書き込み

[Ruby]
file.pos = fp
file.write([w].pack(‘c*’))
[/Ruby]

値を読み取った際にポインタが移動しているので、ここでも書き込み位置を指定します。
読み取りの際、文字列で取得してしまうのと同様、文字列で書き込む必要があります。
数値の配列それぞれの要素を1文字としてpackすることで書き込む値を作っています。

7.ポインタの移動

[Ruby]
fp += 1
[/Ruby]

次の桁に移動します。
ループの終了判定はループ開始直後に行うので、ここでは単に+1します。

8.結果の出力

[Ruby]
puts a
[/Ruby]

最終的に桁があふれたのかどうかは桁フラグが立っているかどうかで判定できるので、桁フラグをそのまま出力します。

動作確認

ちゃんとインクリメントされているかを確認するために、下のようなテキストファイルを用意します。

test.txt
——–
AA
——–

桁上がりしない場合の動作確認は以下のように行いました。

$ cat test.txt
AA
$ ruby binc.rb test.txt
0
$ cat test.txt
BA

桁上がりする場合の動作確認は以下のように行いました。

irb(main):002:0> `cat test.txt`
=> “BA\n”
irb(main):003:0> 256.times do
irb(main):004:1* `ruby binc.rb test.txt`
irb(main):005:1> end
=> 256
irb(main):006:0> `cat test.txt`
=> “BB\n”

桁があふれた場合の動作確認は以下のように行いました。

irb(main):057:0> loop do
irb(main):058:1* r= `ruby binc.rb test.txt`
irb(main):059:1> f= `cat test.txt`
irb(main):060:1> p [r,f]
irb(main):061:1> end

が、一晩経っても終わらないので、試算してみました。
一桁目が約10秒で一周するので、

10(秒)×256×256=655360(秒)
655360(秒)÷60=10922.666..(分)
10927(分)÷60=182.044..(時間)
182(時間)÷24=7.5833..(日)

オゥ…これは待ってられません。
仕方ないので桁があふれる直前の状態のファイルを作って確認します。

irb(main):050:0> filename=”hoge.txt”
=> “hoge.txt”
irb(main):051:0> File.open(filename,”w+b”){|file|
irb(main):052:1* file.pos = 0
irb(main):053:1> file.write([255,255].pack(‘c*’))
irb(main):054:1> }
=> 2
irb(main):055:0> p `ruby binc.rb #{filename}`
“1\n”
=> nil
irb(main):056:0> p `cat #{filename}`
“\000\000”
=> nil

この遅さは問題ですが、今回は以上です。

タグ:

TrackBack