Go path 包详解
概述
path 包实现了用于操作斜杠分隔路径的实用函数。
重要说明:
- ✓ 操作斜杠分隔路径(如 URL 路径)
- ✓ Go 1.0+ 引入
- ✓ 纯词法处理,不访问文件系统
- ✓ 仅处理正斜杠(/)
- ✓ 不处理 Windows 反斜杠路径
- ✓ 轻量级路径操作
重要区别:
- path 包:仅用于斜杠分隔的路径(如 URL 路径)
- path/filepath 包:用于操作操作系统路径(支持 Windows 反斜杠等)
使用场景:
- ✓ URL 路径处理
- ✓ 跨平台路径字符串操作
- ✓ 配置文件路径
- ✗ 文件系统路径(应使用 path/filepath)
包导入
import (
"path"
)
基本使用
1. 路径清理
package main
import (
"fmt"
"path"
)
func main() {
dirty := "/a/b/../c/./d"
clean := path.Clean(dirty)
fmt.Printf("清理前:%s\n", dirty)
fmt.Printf("清理后:%s\n", clean)
}
运行结果:
清理前:/a/b/../c/./d
清理后:/a/c/d
2. 路径连接
package main
import (
"fmt"
"path"
)
func main() {
full := path.Join("a", "b", "c")
fmt.Printf("连接结果:%s\n", full)
}
运行结果:
连接结果:a/b/c
3. 提取文件名和目录
package main
import (
"fmt"
"path"
)
func main() {
p := "/home/user/file.txt"
dir := path.Dir(p)
base := path.Base(p)
ext := path.Ext(p)
fmt.Printf("路径:%s\n", p)
fmt.Printf("目录:%s\n", dir)
fmt.Printf("文件名:%s\n", base)
fmt.Printf("扩展名:%s\n", ext)
}
运行结果:
路径:/home/user/file.txt
目录:/home/user
文件名:file.txt
扩展名:.txt
一、变量
ErrBadPattern
var ErrBadPattern = errors.New("path: malformed pattern")
ErrBadPattern 表示模式格式错误。
说明:
- 当 Match 函数的 pattern 参数格式不正确时返回
- 常见的错误原因:
- 字符类未闭合:
"file[.txt" - 转义字符在末尾:
"file\" - 空的字符类:
"file[]"
- 字符类未闭合:
示例:
_, err := path.Match("file[", "file.txt")
if err == path.ErrBadPattern {
fmt.Println("模式格式错误")
}
二、类型
本包没有导出类型。
三、函数(按 a-z 排序)
Base
func Base(path string) string
Base 返回 path 的最后一个元素。
参数:
path- 输入路径
返回值:
string- 最后一个元素(文件名或最后一段)
处理规则:
- 在提取最后一个元素之前,会删除尾随的斜杠
- 如果路径为空,返回 “.”
- 如果路径完全由斜杠组成,返回 “/”
示例:
fmt.Println(path.Base("/a/b")) // "b"
fmt.Println(path.Base("/a/b/")) // "b"
fmt.Println(path.Base("/")) // "/"
fmt.Println(path.Base("")) // "."
fmt.Println(path.Base("/a/b/c.txt")) // "c.txt"
fmt.Println(path.Base("a/b/c")) // "c"
使用场景:
- 从完整路径提取文件名
- 获取 URL 路径的最后一段
Clean
func Clean(path string) string
Clean 通过纯词法处理返回等价于 path 的最短路径名。
参数:
path- 输入路径
返回值:
string- 清理后的路径
处理规则(迭代应用直到无法继续):
- 将多个斜杠替换为单个斜杠
- 消除每个
.路径名元素(当前目录) - 消除每个内部的
..路径名元素(父目录)及其前面的非..元素 - 消除以根路径开头的
..元素:即路径开头的 “/..” 替换为 “/” - 返回的路径仅在根目录 “/” 时以斜杠结尾
- 如果处理结果为空字符串,返回 “.”
示例:
fmt.Println(path.Clean("/a/b/../c")) // "/a/c"
fmt.Println(path.Clean("/a//b")) // "/a/b"
fmt.Println(path.Clean("/a/./b")) // "/a/b"
fmt.Println(path.Clean("/a/b/.")) // "/a/b"
fmt.Println(path.Clean("")) // "."
fmt.Println(path.Clean("/")) // "/"
fmt.Println(path.Clean("/../a")) // "/a"
fmt.Println(path.Clean("/a/b/c/../../d")) // "/a/d"
复杂示例:
fmt.Println(path.Clean("/a/b/c/./../../g")) // "/a/g"
fmt.Println(path.Clean("mid/content=5/../6")) // "mid/6"
fmt.Println(path.Clean("/../a/b/../././/c")) // "/a/c"
使用场景:
- 规范化路径字符串
- 消除路径冗余
- 安全路径处理(防止目录遍历攻击)
Dir
func Dir(path string) string
Dir 返回 path 除最后一个元素之外的所有部分,通常是路径的目录。
参数:
path- 输入路径
返回值:
string- 目录路径
处理规则:
- 使用 Split 删除最后一个元素
- 清理路径并删除尾随斜杠
- 如果路径为空,返回 “.”
- 如果路径完全由斜杠后跟非斜杠字节组成,返回单个斜杠
- 在其他情况下,返回的路径不以斜杠结尾
示例:
fmt.Println(path.Dir("/a/b")) // "/a"
fmt.Println(path.Dir("/a/b/")) // "/a"
fmt.Println(path.Dir("/")) // "/"
fmt.Println(path.Dir("")) // "."
fmt.Println(path.Dir("a/b")) // "a"
fmt.Println(path.Dir("/a/b/c.txt")) // "/a/b"
与 Base 配合:
p := "/home/user/file.txt"
fmt.Printf("Dir: %s\n", path.Dir(p)) // "/home/user"
fmt.Printf("Base: %s\n", path.Base(p)) // "file.txt"
fmt.Printf("Dir + Base = %s%s\n",
path.Dir(p), path.Base(p)) // "/home/userfile.txt" (注意)
注意:
// Dir 和 Base 的关系不是简单的拼接
p := "/a/b/"
fmt.Println(path.Dir(p)) // "/a"
fmt.Println(path.Base(p)) // "b"
// path.Dir(p) + path.Base(p) != p
Ext
func Ext(path string) string
Ext 返回 path 使用的文件扩展名。
参数:
path- 输入路径
返回值:
string- 文件扩展名(包括点)
处理规则:
- 扩展名从 path 的最后一个斜杠分隔元素的最后一个点开始
- 如果没有点,返回空字符串
- 扩展名包括点前缀
示例:
fmt.Println(path.Ext("/a/b/file.txt")) // ".txt"
fmt.Println(path.Ext("file.txt")) // ".txt"
fmt.Println(path.Ext("/a/b/file.tar.gz")) // ".gz"
fmt.Println(path.Ext("/a/b/file")) // ""
fmt.Println(path.Ext("/a/b/.")) // ""
fmt.Println(path.Ext("/a/b/..")) // ""
fmt.Println(path.Ext(".")) // ""
注意:
// 多个扩展名只返回最后一个
fmt.Println(path.Ext("archive.tar.gz")) // ".gz"
// 点开头但没有其他点
fmt.Println(path.Ext(".bashrc")) // ""
// 点结尾
fmt.Println(path.Ext("file.")) // "."
使用场景:
- 检查文件类型
- 根据扩展名处理文件
- 验证上传文件类型
IsAbs
func IsAbs(path string) bool
IsAbs 报告路径是否为绝对路径。
参数:
path- 输入路径
返回值:
bool- 如果是绝对路径返回 true
判断规则:
- 对于斜杠分隔的路径,如果以 “/” 开头则是绝对路径
示例:
fmt.Println(path.IsAbs("/a/b")) // true
fmt.Println(path.IsAbs("a/b")) // false
fmt.Println(path.IsAbs("/")) // true
fmt.Println(path.IsAbs("")) // false
fmt.Println(path.IsAbs("/home/user")) // true
fmt.Println(path.IsAbs("./file")) // false
fmt.Println(path.IsAbs("../file")) // false
与 filepath.IsAbs 的区别:
// path.IsAbs - 只检查是否以 / 开头
path.IsAbs("/a/b") // true (Unix 绝对路径)
path.IsAbs("C:\\Windows") // false (不是斜杠路径)
// filepath.IsAbs - 检查操作系统的绝对路径
filepath.IsAbs("/a/b") // true (Unix)
filepath.IsAbs("C:\\Windows") // true (Windows)
filepath.IsAbs("\\\\server\\share") // true (Windows UNC)
使用场景:
- 验证路径格式
- 区分相对路径和绝对路径
- URL 路径处理
Join
func Join(elem ...string) string
Join 将任意数量的路径元素连接成单个路径,用斜杠分隔。
参数:
elem- 路径元素的可变参数
返回值:
string- 连接后的路径(已清理)
处理规则:
- 用斜杠分隔各个元素
- 忽略空元素
- 结果会被 Clean 清理
- 如果参数列表为空或所有元素都为空,返回空字符串
示例:
fmt.Println(path.Join("a", "b", "c")) // "a/b/c"
fmt.Println(path.Join("a", "b/c")) // "a/b/c"
fmt.Println(path.Join("a/b", "c")) // "a/b/c"
fmt.Println(path.Join("a", "", "c")) // "a/c"
fmt.Println(path.Join()) // ""
fmt.Println(path.Join("", "")) // ""
fmt.Println(path.Join("/a", "b")) // "/a/b"
fmt.Println(path.Join("a", "/b")) // "a/b"
fmt.Println(path.Join("a", "../b")) // "../b"
特殊行为:
// 第二个及以后的 / 会被吸收
fmt.Println(path.Join("a", "/b")) // "a/b" (不是 "a//b")
// 但 .. 会保留
fmt.Println(path.Join("a", "../b")) // "../b" (不是 "b")
fmt.Println(path.Join("a/b", "../c")) // "a/c"
使用场景:
- 构建 URL 路径
- 连接路径段
- 安全路径拼接
Match
func Match(pattern, name string) (matched bool, err error)
Match 报告 name 是否匹配 shell 模式。
参数:
pattern- 模式字符串name- 要匹配的名称
返回值:
bool- 是否匹配error- 错误(仅当模式格式错误时返回 ErrBadPattern)
模式语法:
pattern:
{ term }
term:
'*' 匹配任何非 / 字符序列
'?' 匹配任何单个非 / 字符
'[' [ '^' ] { character-range } ']'
字符类(必须非空)
c 匹配字符 c (c != '*', '?', '\', '[')
'\' c 匹配字符 c
character-range:
c 匹配字符 c (c != '\', '-', ']')
'\' c 匹配字符 c
lo '-' hi 匹配字符 c (lo <= c <= hi)
示例:
// 星号匹配
matched, _ := path.Match("*.txt", "file.txt")
fmt.Println(matched) // true
matched, _ = path.Match("*.txt", "file.go")
fmt.Println(matched) // false
// 问号匹配
matched, _ = path.Match("file?.txt", "file1.txt")
fmt.Println(matched) // true
matched, _ = path.Match("file?.txt", "file.txt")
fmt.Println(matched) // false (需要至少一个字符)
// 字符类
matched, _ = path.Match("file[0-9].txt", "file5.txt")
fmt.Println(matched) // true
matched, _ = path.Match("file[abc].txt", "fileb.txt")
fmt.Println(matched) // true
// 否定字符类
matched, _ = path.Match("file[!0-9].txt", "filea.txt")
fmt.Println(matched) // true
错误示例:
// 模式格式错误
_, err := path.Match("file[", "file.txt")
fmt.Println(err) // path: malformed pattern
_, err = path.Match("file\\", "file.txt")
fmt.Println(err) // path: malformed pattern
_, err = path.Match("[]", "file.txt")
fmt.Println(err) // path: malformed pattern
使用场景:
- 文件名模式匹配
- 文件过滤
- 通配符搜索
Split
func Split(path string) (dir, file string)
Split 在最后一个斜杠后立即分割 path,将其分为目录和文件名组件。
参数:
path- 输入路径
返回值:
dir- 目录部分(包括最后的斜杠)file- 文件名部分
处理规则:
- 如果 path 中没有斜杠,返回空的 dir 和设置为 path 的 file
- 返回值满足:path = dir + file
示例:
dir, file := path.Split("/a/b/c")
fmt.Printf("dir: %q, file: %q\n", dir, file)
// dir: "/a/b/", file: "c"
dir, file = path.Split("/a/b/")
fmt.Printf("dir: %q, file: %q\n", dir, file)
// dir: "/a/b/", file: ""
dir, file = path.Split("c")
fmt.Printf("dir: %q, file: %q\n", dir, file)
// dir: "", file: "c"
dir, file = path.Split("")
fmt.Printf("dir: %q, file: %q\n", dir, file)
// dir: "", file: ""
与 Dir/Base 的区别:
p := "/a/b/c"
// Split - 一次调用返回两部分
dir, file := path.Split(p)
// dir: "/a/b/", file: "c"
// Dir + Base - 需要两次调用
dir2 := path.Dir(p) // "/a/b"
base2 := path.Base(p) // "c"
// 注意:Split 的 dir 包含尾随斜杠
// 而 Dir 的返回值不包含尾随斜杠
使用场景:
- 分离路径的目录和文件名部分
- 路径重构
- 文件重命名
四、典型示例
示例 1:规范化 URL 路径
package main
import (
"fmt"
"path"
)
func normalizeURLPath(p string) string {
// 清理路径
clean := path.Clean(p)
// 确保以 / 开头
if !path.IsAbs(clean) {
clean = "/" + clean
}
return clean
}
func main() {
paths := []string{
"/api/../api/v1",
"/api//v1//users",
"api/v1",
"/api/./v1/./users",
}
for _, p := range paths {
fmt.Printf("%-25s -> %s\n", p, normalizeURLPath(p))
}
}
运行结果:
/api/../api/v1 -> /api/v1
/api//v1//users -> /api/v1/users
api/v1 -> /api/v1
/api/./v1/./users -> /api/v1/users
示例 2:构建 URL 路径
package main
import (
"fmt"
"path"
)
func buildURL(base string, segments ...string) string {
// 确保 base 以 / 结尾
if base != "" && base[len(base)-1] != '/' {
base += "/"
}
// 连接所有段
return base + path.Join(segments...)
}
func main() {
baseURL := "https://example.com/api"
url1 := buildURL(baseURL, "v1", "users")
fmt.Println(url1) // https://example.com/api/v1/users
url2 := buildURL(baseURL, "v1", "posts", "123")
fmt.Println(url2) // https://example.com/api/v1/posts/123
}
示例 3:文件类型过滤
package main
import (
"fmt"
"path"
)
func filterFiles(files []string, pattern string) ([]string, error) {
var result []string
for _, file := range files {
matched, err := path.Match(pattern, path.Base(file))
if err != nil {
return nil, err
}
if matched {
result = append(result, file)
}
}
return result, nil
}
func main() {
files := []string{
"/docs/readme.txt",
"/docs/notes.md",
"/src/main.go",
"/src/util.go",
"/test.txt",
}
// 过滤 .txt 文件
txtFiles, _ := filterFiles(files, "*.txt")
fmt.Println("TXT 文件:", txtFiles)
// 过滤 .go 文件
goFiles, _ := filterFiles(files, "*.go")
fmt.Println("GO 文件:", goFiles)
// 过滤特定模式
srcFiles, _ := filterFiles(files, "main.*")
fmt.Println("main.* 文件:", srcFiles)
}
运行结果:
TXT 文件:[/docs/readme.txt /test.txt]
GO 文件:[/src/main.go /src/util.go]
main.* 文件:[/src/main.go]
示例 4:路径分析和重构
package main
import (
"fmt"
"path"
)
func analyzePath(p string) {
fmt.Printf("原路径:%s\n", p)
fmt.Printf(" 清理后:%s\n", path.Clean(p))
fmt.Printf(" 绝对路径:%v\n", path.IsAbs(p))
fmt.Printf(" 目录:%s\n", path.Dir(p))
fmt.Printf(" 文件名:%s\n", path.Base(p))
fmt.Printf(" 扩展名:%s\n", path.Ext(p))
dir, file := path.Split(p)
fmt.Printf(" Split: dir=%q, file=%q\n", dir, file)
fmt.Println()
}
func main() {
paths := []string{
"/home/user/documents/report.pdf",
"projects/go/src/main.go",
"/api/v1/users.json",
"./config.yaml",
"../data/file.csv",
}
for _, p := range paths {
analyzePath(p)
}
}
示例 5:安全的文件路径处理
package main
import (
"fmt"
"path"
"strings"
)
// 验证路径是否安全(防止目录遍历攻击)
func isSafePath(basePath, userPath string) bool {
// 清理用户输入
cleanPath := path.Clean(userPath)
// 连接基础路径
fullPath := path.Join(basePath, cleanPath)
// 确保结果仍在 basePath 内
return strings.HasPrefix(fullPath, basePath)
}
func main() {
basePath := "/var/www/html"
testPaths := []string{
"images/logo.png", // 安全
"../etc/passwd", // 危险
"./style.css", // 安全
"../../etc/shadow", // 危险
"js/app.js", // 安全
}
for _, p := range testPaths {
if isSafePath(basePath, p) {
fmt.Printf("✓ %s - 安全\n", p)
} else {
fmt.Printf("✗ %s - 危险\n", p)
}
}
}
运行结果:
✓ images/logo.png - 安全
✗ ../etc/passwd - 危险
✓ ./style.css - 安全
✗ ../../etc/shadow - 危险
✓ js/app.js - 安全
示例 6:批量文件重命名
package main
import (
"fmt"
"path"
"strings"
)
func changeExtension(filePath, newExt string) string {
dir := path.Dir(filePath)
base := strings.TrimSuffix(path.Base(filePath), path.Ext(filePath))
// 确保新扩展名以 . 开头
if !strings.HasPrefix(newExt, ".") {
newExt = "." + newExt
}
return path.Join(dir, base+newExt)
}
func main() {
files := []string{
"/docs/report.txt",
"/docs/notes.md",
"/images/photo.jpg",
"file.tar.gz",
}
fmt.Println("将扩展名改为 .bak:")
for _, f := range files {
newFile := changeExtension(f, "bak")
fmt.Printf(" %s -> %s\n", f, newFile)
}
}
运行结果:
将扩展名改为 .bak:
/docs/report.txt -> /docs/report.bak
/docs/notes.md -> /docs/notes.bak
/images/photo.jpg -> /images/photo.bak
file.tar.gz -> file.tar.bak
示例 7:模式匹配高级用法
package main
import (
"fmt"
"path"
)
func main() {
patterns := []struct {
pattern string
name string
}{
{"*.go", "main.go"},
{"*.go", "main.go.bak"},
{"main.*", "main.go"},
{"main.?", "main.c"},
{"[abc].txt", "a.txt"},
{"[!abc].txt", "d.txt"},
{"file[0-9].txt", "file5.txt"},
{"file[0-9].txt", "file10.txt"},
{"*/*", "a/b"},
{"*/*", "a/b/c"},
}
for _, p := range patterns {
matched, err := path.Match(p.pattern, p.name)
if err != nil {
fmt.Printf("错误:%s 匹配 %s -> %v\n", p.pattern, p.name, err)
} else {
status := "✓"
if !matched {
status = "✗"
}
fmt.Printf("%s %s 匹配 %s = %v\n", status, p.pattern, p.name, matched)
}
}
}
运行结果:
✓ *.go 匹配 main.go = true
✗ *.go 匹配 main.go.bak = false
✓ main.* 匹配 main.go = true
✓ main.? 匹配 main.c = true
✓ [abc].txt 匹配 a.txt = true
✓ [!abc].txt 匹配 d.txt = true
✓ file[0-9].txt 匹配 file5.txt = true
✗ file[0-9].txt 匹配 file10.txt = false
✓ */* 匹配 a/b = true
✗ */* 匹配 a/b/c = false
五、最佳实践
1. 使用 Clean 规范化路径
// ✓ 推荐 - 总是清理用户输入
userInput := "/api/../api/v1"
safePath := path.Clean(userInput)
// ✗ 不推荐 - 直接使用未清理的路径
unsafePath := userInput // 可能包含 .. 等
2. 使用 Join 而非字符串拼接
// ✓ 正确 - 使用 Join
fullPath := path.Join(base, subpath)
// ✗ 错误 - 字符串拼接可能导致多个斜杠
fullPath := base + "/" + subpath // 可能有 //
3. 检查 IsAbs 验证路径
// ✓ 正确 - 验证路径类型
if !path.IsAbs(p) {
p = "/" + p // 转换为绝对路径
}
// ✗ 错误 - 假设路径格式
// 直接使用 p,可能是相对路径
4. Split 和 Dir/Base 的选择
// 需要同时获取目录和文件名
// ✓ 使用 Split(一次调用)
dir, file := path.Split(p)
// 只需要目录或文件名
// ✓ 使用 Dir 或 Base
dir := path.Dir(p)
base := path.Base(p)
5. 处理扩展名
// ✓ 正确 - 检查扩展名
if path.Ext(file) == ".txt" {
// 处理文本文件
}
// ✓ 正确 - 移除扩展名
name := strings.TrimSuffix(file, path.Ext(file))
// ✗ 错误 - 手动处理扩展名
dotIndex := strings.LastIndex(file, ".")
ext := file[dotIndex:] // 可能越界
6. 安全的路径处理
// ✓ 正确 - 防止目录遍历
func safePath(base, user string) string {
clean := path.Clean(user)
full := path.Join(base, clean)
if !strings.HasPrefix(full, base) {
return "" // 拒绝访问
}
return full
}
// ✗ 错误 - 不验证路径
full := path.Join(base, user) // 可能访问 basePath 外的文件
7. 使用 Match 进行模式匹配
// ✓ 正确 - 检查错误
matched, err := path.Match(pattern, name)
if err != nil {
log.Printf("模式错误:%v", err)
return
}
// ✗ 错误 - 忽略错误
matched, _ := path.Match(pattern, name)
// 如果模式错误,matched 始终为 false
六、与其他包配合
1. 与 strings 包配合
import (
"path"
"strings"
)
// 移除扩展名
name := strings.TrimSuffix("file.txt", path.Ext("file.txt"))
// 检查前缀
if strings.HasPrefix(path.Base(file), "temp_") {
// 临时文件
}
2. 与 net/url 包配合
import (
"net/url"
"path"
)
u, _ := url.Parse("https://example.com/a/b/../c")
cleanPath := path.Clean(u.Path) // "/a/c"
u.Path = cleanPath
3. 与 io/fs 包配合
import (
"io/fs"
"path"
)
// 遍历文件系统
fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if path.Ext(path) == ".txt" {
// 处理文本文件
}
return nil
})
4. 与 path/filepath 包配合
import (
"path" // URL 路径
"path/filepath" // 文件系统路径
)
// URL 路径使用 path
urlPath := path.Join("api", "v1", "users")
// 文件系统路径使用 filepath
filePath := filepath.Join("docs", "readme.txt")
七、快速参考
函数总览
| 函数 | 说明 | 返回值 |
|---|---|---|
| Base | 返回路径的最后一个元素 | string |
| Clean | 清理路径(消除 .. 和 .) | string |
| Dir | 返回路径的目录部分 | string |
| Ext | 返回文件扩展名 | string |
| IsAbs | 检查是否为绝对路径 | bool |
| Join | 连接路径元素 | string |
| Match | 模式匹配 | (bool, error) |
| Split | 分割为目录和文件名 | (string, string) |
常见路径操作
| 操作 | 函数 | 示例 |
|---|---|---|
| 获取文件名 | Base | Base("/a/b.txt") → "b.txt" |
| 获取目录 | Dir | Dir("/a/b.txt") → "/a" |
| 获取扩展名 | Ext | Ext("/a/b.txt") → ".txt" |
| 清理路径 | Clean | Clean("/a/../b") → "/b" |
| 连接路径 | Join | Join("a", "b") → "a/b" |
| 分割路径 | Split | Split("/a/b") → ("/a/", "b") |
| 检查绝对 | IsAbs | IsAbs("/a") → true |
| 模式匹配 | Match | Match("*.txt", "a.txt") → true |
路径清理规则
| 规则 | 示例 | 结果 |
|---|---|---|
| 多个斜杠 | a//b | a/b |
| 当前目录 | a/./b | a/b |
| 父目录 | a/b/.. | a |
| 根目录父目录 | /../a | /a |
| 空路径 | `` | . |
模式匹配语法
| 模式 | 说明 | 示例 |
|---|---|---|
* | 匹配任何非 / 序列 | *.txt 匹配 file.txt |
? | 匹配单个非 / 字符 | file? 匹配 file1 |
[abc] | 匹配字符类 | [ab].txt 匹配 a.txt |
[!abc] | 匹配否定字符类 | [!ab].txt 匹配 c.txt |
[0-9] | 匹配字符范围 | file[0-9] 匹配 file5 |
\c | 转义字符 | file\* 匹配 file* |
path vs filepath
| 特性 | path | path/filepath |
|---|---|---|
| 路径分隔符 | / (正斜杠) | / 或 \ (操作系统) |
| Windows 支持 | 否 | 是 |
| 用途 | URL 路径 | 文件系统路径 |
| 绝对路径判断 | 以 / 开头 | 操作系统相关 |
| 跨平台 | 是 | 否(依赖 OS) |
八、注意事项
1. path 与 filepath 的区别
// path - 仅处理斜杠路径
path.Join("a", "b") // "a/b"
path.IsAbs("/a") // true
path.IsAbs("C:\\Windows") // false
// filepath - 处理操作系统路径
filepath.Join("a", "b") // "a\b" (Windows)
filepath.IsAbs("/a") // true (Unix)
filepath.IsAbs("C:\\Windows") // true (Windows)
2. Clean 不访问文件系统
// Clean 只做词法处理,不检查路径是否存在
clean := path.Clean("/nonexistent/../file")
// 结果:"/file" (即使路径不存在)
3. Join 的特殊行为
// 第二个及以后的 / 会被吸收
path.Join("a", "/b") // "a/b" (不是 "a//b")
// 但 .. 会保留
path.Join("a", "../b") // "../b" (不是 "b")
4. Dir 和 Base 不是简单的分割
p := "/a/b/"
fmt.Println(path.Dir(p)) // "/a" (不是 "/a/b")
fmt.Println(path.Base(p)) // "b"
// Dir 会删除尾随斜杠
// Base 会删除尾随斜杠后取最后元素
5. Ext 的行为
// 没有点返回空
path.Ext("file") // ""
// 点开头但没有其他点
path.Ext(".bashrc") // ""
// 多个点只返回最后一个扩展名
path.Ext("file.tar.gz") // ".gz"
// 点结尾
path.Ext("file.") // "."
6. Match 需要完全匹配
// Match 要求模式匹配整个名称,不只是子串
path.Match("*.txt", "file.txt") // true
path.Match("*.txt", "file.txt.bak") // false
// 要匹配子串,需要自己添加 *
path.Match("*.txt*", "file.txt.bak") // true
7. Split 的返回值
// Split 的 dir 包含尾随斜杠
dir, file := path.Split("/a/b")
// dir: "/a/", file: "b"
// 而 Dir 不包含尾随斜杠
fmt.Println(path.Dir("/a/b")) // "/a"
8. 空路径处理
// 空路径的特殊处理
path.Base("") // "."
path.Dir("") // "."
path.Clean("") // "."
path.IsAbs("") // false
path.Join("") // ""
path.Split("") // ("", "")
9. 模式匹配错误
// 只返回 ErrBadPattern 一种错误
_, err := path.Match("file[", "file.txt")
if err == path.ErrBadPattern {
// 模式格式错误
}
// 不匹配不会返回错误
matched, err := path.Match("*.txt", "file.go")
// matched: false, err: nil
10. 跨平台考虑
// path 包在所有平台上行为一致
// 适合处理 URL 路径和配置文件路径
// filepath 包依赖操作系统
// 适合处理文件系统路径
// ✓ 推荐 - 根据用途选择
urlPath := path.Join("api", "v1") // URL 路径
filePath := filepath.Join("docs", "readme.txt") // 文件路径
最后更新: 2026-04-05
Go 版本: Go 1.0+
包文档: https://pkg.go.dev/path
相关包: path/filepath, net/url, io/fs
相关文档: Rob Pike, “Lexical File Names in Plan 9”, https://9p.io/sys/doc/lexnames.html