Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 - 清理后的路径

处理规则(迭代应用直到无法继续):

  1. 将多个斜杠替换为单个斜杠
  2. 消除每个 . 路径名元素(当前目录)
  3. 消除每个内部的 .. 路径名元素(父目录)及其前面的非 .. 元素
  4. 消除以根路径开头的 .. 元素:即路径开头的 “/..” 替换为 “/”
  5. 返回的路径仅在根目录 “/” 时以斜杠结尾
  6. 如果处理结果为空字符串,返回 “.”

示例:

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)

常见路径操作

操作函数示例
获取文件名BaseBase("/a/b.txt")"b.txt"
获取目录DirDir("/a/b.txt")"/a"
获取扩展名ExtExt("/a/b.txt")".txt"
清理路径CleanClean("/a/../b")"/b"
连接路径JoinJoin("a", "b")"a/b"
分割路径SplitSplit("/a/b")("/a/", "b")
检查绝对IsAbsIsAbs("/a")true
模式匹配MatchMatch("*.txt", "a.txt")true

路径清理规则

规则示例结果
多个斜杠a//ba/b
当前目录a/./ba/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

特性pathpath/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