ファイルを開く際のモード指定の罠
いやぁハマった。
RubyでFile.open()する際のモードの話。結論から言うと、"r+"が万能で、"w+"はすでにファイルがあるときは0バイトにされ、"a+"は末尾にしか書けない。
リファレンスはこれ。読んだつもりだったけど理解が不十分だったらしい。
やりたいこと
1MiBのすでに存在するファイルのうち、128バイト目から136バイト目までを"DeAdBeEf"に書き換えたい。
$ dd if=/dev/zero of=1MiB.dat bs=1M count=1 $ hexdump -C 1MiB.dat 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00100000
r+の動作
"READ"というイメージに反して、ちゃんと書き込める。
irb(main):001:0> fp=File.open("1MiB.dat", "r+") => #<File:1MiB.dat> irb(main):002:0> fp.pos => 0 irb(main):003:0> fp.seek(128) => 0 irb(main):004:0> fp.pos => 128 irb(main):005:0> fp.write("DeAdBeEf") => 8 irb(main):006:0> fp.pos => 136 irb(main):007:0> fp.close => nil $ hexdump -C 1MiB.dat 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00000080 44 65 41 64 42 65 45 66 00 00 00 00 00 00 00 00 |DeAdBeEf........| 00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00100000 ### ←ファイルはぴったり1MiBある
w+の動作
うっかり間違えると悲劇。どんな大きなファイルでも、途端に0バイトにしてしまう。
irb(main):001:0> fp=File.open("1MiB.dat", "w+") => #<File:1MiB.dat> irb(main):002:0> fp.pos => 0 irb(main):003:0> fp.seek(128) => 0 irb(main):004:0> fp.pos => 128 irb(main):005:0> fp.write("DeAdBeEf") => 8 irb(main):006:0> fp.pos => 136 irb(main):007:0> fp.close => nil $ hexdump -C 1MiB.dat 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00000080 44 65 41 64 42 65 45 66 |DeAdBeEf| 00000088 ### ←0x88バイトでファイルが終わっている
a+の動作
一番のくせ者。"APPEND"は、末尾以外にデータを書き込もうとしても、書き込めたように見えて、末尾に書かれてしまう。
どうやらRubyの問題ではなく、Cのfopen()レベルの問題らしい。書き込み動作が行われる前に、必ず末尾へのseekが入るようだ。man fopenを参照。
irb(main):001:0> fp=File.open("1MiB.dat", "a+") => #<File:1MiB.dat> irb(main):002:0> fp.pos => 0 irb(main):003:0> fp.seek(128) => 0 irb(main):004:0> fp.pos => 128 ### ←うn。 irb(main):005:0> fp.write("DeAdBeEf") => 8 irb(main):006:0> fp.pos => 1048584 ### ←うn!? irb(main):007:0> fp.close => nil irb(main):008:0> exit $ hexdump -C 1MiB.dat 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00100000 44 65 41 64 42 65 45 66 |DeAdBeEf| 00100008 ### ←ファイルサイズが8バイト増えている