この記事について
go 言語でアプリケーションを書いていて、ファイルを touch したくなった。
でも、 os
には touch
がない。
どうしたものかとツイートしたら
という応答を頂いたので、書いてみた。
ソースコード
package gotouch
import (
"runtime"
"syscall"
"time"
)
// Touch は、 macOS などに含まれる touch コマンドを模倣する
func Touch(path string) error {
now := time.Now().UnixNano()
const nano = 1000 * 1000 * 1000
t := syscall.Timespec{Sec: now / nano, Nsec: now % nano}
ut := []syscall.Timespec{t, t}
err := syscall.UtimesNano(path, ut)
if err == nil {
return nil
}
isNoEnt := func(e error) bool {
if e == syscall.ENOENT {
return true
}
switch runtime.GOOS {
case "windows":
// Windows では syscall.ERROR_PATH_NOT_FOUND を返すことがある。
// しかし、非 Windows 環境では syscall.ERROR_PATH_NOT_FOUND は定義されない。
// ifdef も使えない。別ファイルにするのはめんどくさい。
// 仕方ないので自分で定義する。
const ERROR_PATH_NOT_FOUND = 3 // 「don't use ALL_CAPS in Go names」と言われるが、 syscall に合わせる。
if errno, ok := e.(syscall.Errno); ok && errno == ERROR_PATH_NOT_FOUND {
return true
}
return false
case "darwin":
return false
case "linux":
// たぶん OK だけど、テストしてない。
return false
default:
panic("you should write something here")
}
}
if !isNoEnt(err) {
return err
}
fd, err := syscall.Open(path, syscall.O_CREAT|syscall.O_RDWR, 0644)
if err != nil {
return err
}
defer syscall.Close(fd)
return nil
}
かるく説明
日本語で touch の説明をすると
- ファイルがあれば、そのファイルのタイムスタンプを更新する
- なければ、ファイルを作る
という感じになると思う。
しかし、上記の日本語のとおりに実装すると、《ファイルがある》と思ってから《タイムスタンプを更新する》という操作の間に誰かがファイルを削除する可能性があり、よろしくない。
そこで実際は
- タイムスタンプの更新を試みる
- 「ファイルがない」という理由で更新に失敗した場合、
O_TRUNC
を指定せずにファイルを作る
という実装になる。
O_TRUNC
なしで作ることによって《「ファイルがない」という理由で更新に失敗》のあと、《ファイルを作る》という操作の前に誰かがファイルを作った場合でも不幸になることがない。
なお。
上記のソースコードは手元の macOS と Windows で動作確認した。
Linux では動かしてないけど大丈夫じゃないかと思っている。
macOS, Windows, Linux 以外だと死ぬ。