filepath.Join 使って下さい。 / “Go言語でクロスプラットフォーム的にパスとファイル名を結合するには? | 非IT企業に勤める中年サラリーマンのIT日記” https://t.co/ehXCf0u8X9
— 自称mattn (@mattn_jp) October 24, 2017
パスからファイル名を得る
filepath.Base
を使う。
package main
import (
"path/filepath"
)
func main() {
println(filepath.Base("C:/foo/bar"))
}
この場合 bar
が表示される。
パスからディレクトリ名を得る
filepath.Dir
を使う。
package main
import (
"path/filepath"
)
func main() {
println(filepath.Dir(filepath.Clean(`../foo\bar`)))
}
この場合、..\foo
が表示される。
パスからボリューム名を得る
filepath.VolumeName
を使う。
package main
import (
"path/filepath"
)
func main() {
println(filepath.VolumeName(`c:/windows/notepad.exe`))
}
この場合 c:
が表示される。UNIX の場合は空文字列が返る。
相対パスから絶対パスに変換する
filepath.Abs
を使う。
package main
import (
"log"
"path/filepath"
)
func main() {
p, err := filepath.Abs("./testdata")
if err != nil {
log.Fatal(err)
}
println(p)
}
絶対パスから相対パスに変換する
filepath.Rel
を使う。
package main
import (
"log"
"os"
"path/filepath"
)
func main() {
cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
p, err := filepath.Rel(cwd, `c:/dev`)
if err != nil {
log.Fatal(err)
}
println(p)
}
ディレクトリ配下であれば、それ以下の部分が。ディレクトリ配下でなければ ..
で上昇した結果が返る。
パスを綺麗にする
../foo\bar\baz
といった汚いパスを綺麗にするには filepath.Clean
を使う。
package main
import (
"path/filepath"
)
func main() {
println(filepath.Clean(`../foo\bar`))
}
この場合、..\foo\bar
が表示される。
シンボリックリンクのリンク元を得る
filepath.EvalSymlinks
を使う。
package main
import (
"log"
"path/filepath"
)
func main() {
p, err := filepath.EvalSymlinks(`c:/dirlink`)
if err != nil {
log.Fatal(err)
}
println(p)
}
Windows でも動作する。(ショートカットファイルではなくジャンクション)
パスから拡張子を得る
filepath.Ext
を使う。
package main
import (
"path/filepath"
)
func main() {
println(filepath.Ext(`C:\Windows\Notepad.exe`))
}
.bashrc
の様にドットで始まるファイル名を渡すと、ファイル名のまま返る。
スラッシュで区切られたパスを OS のパスセパレータに直す
filepath.FromSlash
を使う。僕が path/filepath で一番好きな関数。
package main
import (
"path/filepath"
)
func main() {
println(filepath.FromSlash(`c:/users/mattn/.bashrc`))
}
この場合 c:\users\mattn\.bashrc
が表示される。UNIX では何もしていない。Windows だけスラッシュがバックスラッシュに変換される。なので例えばファイルパスから URL のパスを作る時にこれを使ってくれると Windows ユーザが幸せになれる。
OS のパスセパレータで区切られたパスをスラッシュに直す
filepath.ToSlash
を使う。filepath.FromSlash
の逆。
package main
import (
"path/filepath"
)
func main() {
println(filepath.ToSlash(`c:\users\mattn\.bashrc`))
}
c:/users/mattn/.bashrc
が表示される。
ファイルをマスクで検索する
filepath.Glob
を使う。
package main
import (
"log"
"path/filepath"
)
func main() {
files, err := filepath.Glob(`c:\Windows\*`)
if err != nil {
log.Fatal(err)
}
for _, f := range files {
println(f)
}
}
Windows の場合、バックスラッシュはエスケープ文字として扱われない。また **
は使えない。使いたい場合は zglob を使う。
パスの先頭に特定のディレクトリが含まれるか確認する
filepath.HasPrefix
を使う。
package main
import (
"path/filepath"
)
func main() {
println(filepath.HasPrefix(`c:\Windows\Notepad.exe`, `c:\windows`))
}
気を付けないといけないのは、この動作は strings.HasPrefix でしかない事。この関数は deprecated として扱われている。
パスが絶対パスかを確認する
filepath.IsAbs
を使う。
package main
import (
"path/filepath"
)
func main() {
println(filepath.IsAbs(`..\Notepad.exe`))
}
パスを結合する
filepath.Join
を使う。
package main
import (
"path/filepath"
)
func main() {
println(filepath.Join(`c:\windows`, `system32`, `drivers`, `etc`, `hosts`))
}
OS のパスセパレータで結合される。可変個引数なので複数渡せる。
パスをディレクトリ名とファイル名に分解する
filepath.Split
を使う。
package main
import (
"path/filepath"
)
func main() {
dir, filename := filepath.Split(`c:\windows\notepad.exe`)
println(dir, filename)
}
c:\windows\
と notepad.exe
に分けられる。
パスリストを分解する
PATH 環境変数の様に OS のパスリストセパレータで結合された物を分解する。filepath.SplitList
を使う。
package main
import (
"os"
"path/filepath"
)
func main() {
for _, p := range filepath.SplitList(os.Getenv("PATH")) {
println(p)
}
}
パスがパターンにマッチするか確認する
filepath.Match
を使う。Glob が中で使っている物に過ぎない。
package main
import (
"log"
"path/filepath"
)
func main() {
ok, err := filepath.Match(`*.exe`, `c:/windows/notepad.exe`)
if err != nil {
log.Fatal(err)
}
println(ok)
}
ディレクトリを下ってファイルを探索する
filepath.Walk
を使う。例えば特定パス配下のディレクトリだけを探すのであれば以下の様に実行する。
package main
import (
"log"
"os"
"path/filepath"
)
func main() {
root := `c:\windows\system32\drivers\`
err := filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
if info.IsDir() {
println(p)
}
return nil
})
if err != nil {
log.Fatal(err)
}
}
探索を中断するには関数内でエラーを返す。もし特定のディレクトリ配下の探索をやめたいのであれば filepath.SkipDir
を return で返してあげる。
なお物理ファイルの操作に path/filepath ではなく path を使うと爆発します。ちなみに、なぜここまで口をすっぱく言っているのかと言うと、UNIX で実装した物を Windows に持ってくると動かないからです。それどころかセキュリティ issue にもなり得る。
package main
import (
"io"
"net/http"
"os"
"path"
)
func main() {
cwd, _ := os.Getwd()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if ok, err := path.Match("/download/*", r.URL.Path); err != nil || !ok {
http.NotFound(w, r)
return
}
name := path.Join(cwd, r.URL.Path)
f, err := os.Open(name)
if err != nil {
http.NotFound(w, r)
return
}
defer f.Close()
io.Copy(w, f)
})
http.ListenAndServe(":8080", nil)
}
何かをダウンロードさせるのにこういったコードを書いてしまうと、以下の様なリクエストでディレクトリトラバーサルが発生する。(正しくは http.ServeFile
を使ってね)
http://localhost:8080/download/..%5cmain.go
これは Go のライブラリが悪い訳じゃない。こんなコードを書いた人が悪い。この辺は「みんなの Go 言語」にも書かれている。