2017/10/24

Recent entries from same category

  1. Go 言語プログラミングエッセンスという本を書きました。
  2. errors.Join が入った。
  3. unsafe.StringData、unsafe.String、unsafe.SliceData が入った。
  4. Re: Go言語で画像ファイルか確認してみる
  5. net/url に JoinPath が入った。

たぶん逆引きが無いから探せないのかなと思ったので path/filepath にどういう機能があるのか書いておく。

パスからファイル名を得る

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 errorerror {
        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 言語」にも書かれている。

Posted at by | Edit