症状
準備
以下のようなログラムを作成した.
package main
import (
"fmt"
"io"
"os"
"path/filepath"
)
func main() {
path := "~/.config/tilde-expan/hoge.txt"
write(path)
read(path)
}
func write(path string) {
dir := filepath.Dir(path) // ディレクトリが存在しない場合は作成する
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0755)
}
file, _ := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
defer file.Close()
fmt.Fprintln(file, "test")
}
func read(path string) {
file, _ := os.Open(path)
defer file.Close()
byteData, _ := io.ReadAll(file)
content := string(byteData)
fmt.Println("content = ", content)
}
実行の流れは以下の通り.
- main開始.
- write呼び出し.
- ディレクトリが存在しなければ作成する.
- 指定したパスに"test"と書き込む.
- read呼び出し.
- 指定したパスのファイル内容を読み込む.
- 読み込んだ内容を出力する.
実行結果
以下のように,特にエラーも出ず,書き込んだファイルの中身を表示することができる.
$ go run main.go
content = test
しかし,保存先であるフォルダに移動しようとしてもフォルダやファイルは存在しない.
$ cd ~/.config/tilde-expan
cd: no such file or directory: /Users/USERNAME/.config/tilde-expan
$ cat cd ~/.config/tilde-expan/hoge.txt
cat: cd: No such file or directory
cat: /Users/USERNAME/.config/tilde-expan/hoge.txt: No such file or directory
デバッグ
プログラム
以下のように,各所でデバッグプリントを入れてみる.
package main
import (
"fmt"
"io"
"os"
"path/filepath"
)
func main() {
path := "~/.config/tilde-expan/hoge.txt"
write(path)
read(path)
}
func write(path string) {
dir := filepath.Dir(path) // ディレクトリが存在しない場合は作成する
if _, err := os.Stat(dir); os.IsNotExist(err) {
fmt.Println("isNotExist", err)
err = os.MkdirAll(dir, 0755)
fmt.Println("MkdirAll", err)
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
fmt.Println("OpenFile", err)
defer file.Close()
_, err = fmt.Fprintln(file, "test")
fmt.Println("Fprintln", err)
}
func read(path string) {
file, err := os.Open(path)
fmt.Println("Open", err)
defer file.Close()
byteData, err := io.ReadAll(file)
fmt.Println("ReadAll", err)
content := string(byteData)
fmt.Println("content = ", content)
}
結果
各エラー出力は全て<nil>
.
ついでにcontetn
が2行に増えているので,正しく追記できていることもわかる.
$ go run main.go
isNotExist stat ~/.config/tilde-expan: no such file or directory
MkdirAll <nil>
OpenFile <nil>
Fprintln <nil>
Open <nil>
ReadAll <nil>
content = test
test
原因
Goのパス指定はチルダに対応していない.
これだけだった.
Goのプログラムと同じ場所でls
コマンドを実行すると,同じ場所に~
ディレクトリが生成されている.
$ ls
README.md main.go ~
~
がエクスパンドされないようにパスを"
で囲んでcat
コマンドを実行すると,先ほど書き込んだファイルの中身が表示される.
$ cat "~/.config/tilde-expan/hoge.txt"
test
tes
つまり,~
がホームディレクトリとして認識されず,そのまま~
ディレクトリだと認識されていた.
解決策
~
エクスパンド処理を追加する.
以下のような関数を作る.
func expandTilde(path string) (string, error) {
if path == "~" || strings.HasPrefix(path, "~/") {
user, err := user.Current()
if err != nil {
return "", err
}
if path == "~" {
return user.HomeDir, nil
}
return filepath.Join(user.HomeDir, path[2:]), nil // ~/ を切り取る
}
return path, nil
}
以下のようにしてmain
関数に組み込めむ.
func main() {
path := "~/.config/tilde-expan/hoge.txt"
actualPath, _ := expandTilde(path)
fmt.Println("actualPath = ", actualPath)
write(actualPath)
read(actualPath)
}
パスが正しく変換できていることがわかる.
また,目的通りのパスに保存されていることもわかる.
$ go run ./main.go
actualPath = /Users/USERNAME/.config/tilde-expan/hoge.txt
content = test
$ cat ~/.config/tilde-expan/hoge.txt
test
サンプルプログラム