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 参数必须检查 |