Skip to content
Go
path/filepath — 跨平台路径操作

path/filepath — 跨平台路径操作

path/filepath 根据操作系统自动使用正确的路径分隔符(/\)。处理文件路径时必须使用它。


1. 核心函数

// 拼接路径(唯一推荐方式)
path := filepath.Join("a", "b", "c")     // "a/b/c" (Linux) 或 "a\b\c" (Windows)

// 分割
dir, file := filepath.Split("/usr/local/bin/go")  // dir="/usr/local/bin/", file="go"

// 扩展名
ext := filepath.Ext("main.go")           // ".go"
name := strings.TrimSuffix("main.go", ext) // "main"

// 最后和不含
filepath.Base("/usr/local/bin/go")       // "go"
filepath.Dir("/usr/local/bin/go")        // "/usr/local/bin"

// 规范化
filepath.Clean("a/./b/../c")             // "a/c"

// 获取绝对路径
// Abs: 将相对路径转为绝对路径(从当前工作目录出发)
// 如果传入的是绝对路径,Abs 会先 Clean 再返回
abs, _ := filepath.Abs(".")              // 假设当前在 /home/user/project → "/home/user/project"
abs, _ := filepath.Abs("config/app.yaml")  // → "/home/user/project/config/app.yaml"
abs, _ := filepath.Abs("/usr/local/bin")   // → "/usr/local/bin"(已经是绝对路径,只做 Clean)

// 计算相对路径
// Rel: 计算从 basepath 到 targpath 的相对路径
// 结果 = 从 basepath 出发,怎么走到 targpath
rel, _ := filepath.Rel("/a/b", "/a/c/d")     // → "../c/d"(从 b 出发:先回退到 a,再进入 c/d)
rel, _ := filepath.Rel("/a/b", "/a/b/c/d")   // → "c/d"(从 b 出发:直接在 b 下面找 c/d)
rel, _ := filepath.Rel("/a/b/c", "/a/b")     // → ".."(从 c 回退到 b)
rel, _ := filepath.Rel("/a/b", "/x/y")       // → "../../x/y"(需要多级回退)
// 🚨 Rel 的两个路径都必须是 Clean 过的,且 basepath 如果是相对路径会返回错误

🚨 黄金法则:绝不用字符串拼接路径(dir + "/" + file),始终用 filepath.Join


2. 路径遍历安全

// 防止目录遍历攻击(../../etc/passwd)
func safeReadFile(baseDir, userPath string) ([]byte, error) {
    // 清理用户输入
    clean := filepath.Clean(userPath)

    // 拼接到基础目录
    fullPath := filepath.Join(baseDir, clean)

    // 验证结果仍在基础目录内
    if !strings.HasPrefix(fullPath, filepath.Clean(baseDir)) {
        return nil, fmt.Errorf("invalid path: %s", userPath)
    }

    return os.ReadFile(fullPath)
}

3. 目录遍历

3.1 WalkDir(推荐,Go 1.16+)

filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
    if err != nil {
        return err // 无法访问的路径
    }
    if d.IsDir() {
        fmt.Println("DIR:", path)
    } else {
        fmt.Println("FILE:", path)
        info, _ := d.Info()
        fmt.Println("  Size:", info.Size())
    }
    return nil
})

3.2 更高效的 WalkDirFunc

// 跳过不需要的目录
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
    if d.IsDir() && d.Name() == ".git" {
        return filepath.SkipDir // 跳过 .git 目录
    }
    return nil
})

4. Glob 模式匹配

// 基本匹配
files, _ := filepath.Glob("*.go")         // 当前目录的所有 .go 文件
files, _ := filepath.Glob("data/*.csv")   // data/ 下的 CSV

// 匹配单个字符
files, _ := filepath.Glob("file_?.txt")   // file_1.txt, file_a.txt

// 注意:不支持 **(递归匹配)
// 需要递归用 filepath.WalkDir + 手动过滤

💡 替代方案fs.Glob(fsys, pattern) 接受 fs.FS,更通用。


5. path 包 vs path/filepath 包

特性 path path/filepath
路径分隔符 始终 / OS 自适应
使用场景 URL 路径、HTTP 路由 文件系统路径
Join a/b/c Linux: a/b/c, Windows: a\b\c
Ext

💡 规范:处理文件系统路径用 path/filepath,处理 URL 或用 / 的逻辑路径用 path


6. 🚨 常见陷阱

陷阱 说明
硬编码路径分隔符 filepath.Join 替代字符串拼接
Windows 上的 :\\ filepath.VolumeName 处理盘符
filepath.Clean 不验证路径存在 只是语法清理,不检查文件系统
WalkDir 中处理错误 回调的 err 参数必须检查