はじめに
最近というほどでもないがfileを開いて編集したあと上書きをして保存するみたいなプログラムを実装した.
そのときにとあるバグを仕込んでしまったので書いておく.
ファイルを開くときについて
C言語とかを書いたことがある人ならわかると思うだろうが, fileを開いたときにはどういうmodeでファイルを開くかというmodeが存在する.
それがGo言語だろうがC言語だろうがOSがそういうふうにできていれば関係なく存在する.
例えばGo言語でfileをreadonlyとして開くときはしばしば os.Open
関数を利用する.
f, err := os.Open("適当なfile名")
if err != nil {
// なんかエラー処理
}
...
os.Open
の中身は次のようになっている.
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
O_RDONLY
がopen modeになっている.
ちなみに O_RDONLY
以外はこのようになっている.
mode type | 意味 |
---|---|
O_RDONLY | 読み込み可 |
O_WRONLY | 書き込み可 |
O_RDWR | 読み書き可 |
詳しいいことは他の資料をあさって読んだほうがよく分かると思う.
Go言語でファイルを開くときは他にもいくつか方法がある.
例えば os.Create
などだ.
os.Create
の内部的には次のようになっている.
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
os.Create
は読み書き可能状態でファイルを開くが存在しないときはファイルを作成するし,
存在した場合は中に書かれている内容を消してから開く.
// 一回目実行すると `test.txt` が作成され中に `hoge` という文字列が記述される
package main
import "os"
func main() {
f, err := os.Create("test.txt")
if err != nil {
panic(err)
}
f.Write([]byte("hoge"))
}
// test.txt がある状態で実行すると `O_TRUNC` が発動して中の文字列が消える
package main
func main() {
os.Create("test.txt")
}
os.Open
, os.Create
に共通して言えることは, どちらも os.OpenFile
を利用していることである.
問題が起きるときについて
自分でテキストエディタを実装したいときを考えてみる.
多分こんなかんじになる.
os.Open
でファイルを開く- 編集する
os.OpenFile
で書き込み可能な状態で開く- 書き込み可能な状態で開いたやつに書き込む
はじめ私はこんな具合で実装していた.
r, err := os.Open("適当なファイル名")
if err != nil {
panic(err)
}
defer r.Close()
buffer := new(bytes.Buffer)
io.Copy(buffer, r)
// なんか編集する処理でbufferを編集
w, err := os.OpenFile(fileName, os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer w.Close()
io.Copy(w, buffer)
この処理はあまり良くない.
編集前の結果より編集後の結果が長くなるときは問題ないが, 編集前の結果より編集後の結果が長く為るとき問題となる.
どういうことか説明しよう.
前提条件として test.txt
には hogehoge
という文字列が書き込まれているものとする.
まず問題がないときのケースだ.
editedData := []byte("hugahugahuga")
w, err := os.OpenFile(fileName, os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
w.Write(editedData)
この場合は test.txt
の中身は hugahugahuga
になる.
問題ある場合だとこの様になる.
editedData := []byte("huga")
w, err := os.OpenFile(fileName, os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
w.Write(editedData)
test.txt
の中身は hugahoge
になってしまう.
問題の回避
このような自体を回避するためには, O_TRUNC
を使う.
開いたときに前の文字列が消えるので古い文字列が残ることはない.