Go 跨平台路径入门:filepath、path 和文件名处理

本文讲解 Go 中 filepath 和 path 的区别,说明如何拼接本地文件路径、处理扩展名、遍历目录并避免跨平台路径问题。

路径问题经常在换系统后暴露

很多 Go 初学者在 macOS 或 Linux 上写路径时,会直接拼字符串:

path := dir + "/" + filename

这在类 Unix 系统上通常能跑,但到了 Windows,路径分隔符、盘符、绝对路径规则都可能不同。Go 标准库提供了 path/filepath 专门处理本地文件路径,提供了 path 处理 URL 路径这类斜杠分隔路径。分清它们,能避免很多跨平台问题。

这篇文章讲本地路径拼接、扩展名处理、目录遍历和安全边界。它看起来基础,但命令行工具、静态文件服务、上传处理、配置读取都会用到。

filepath.Join 拼接本地路径

fullPath := filepath.Join("data", "users", "profile.json")
fmt.Println(fullPath)

在 Unix 系统上输出:

data/users/profile.json

在 Windows 上会使用反斜杠。不要自己拼 /

清理路径:

clean := filepath.Clean("data/../data/users/./profile.json")
fmt.Println(clean)

判断是否绝对路径:

fmt.Println(filepath.IsAbs("/tmp/app.log"))

获取目录和文件名:

dir := filepath.Dir("/var/log/app.log")
base := filepath.Base("/var/log/app.log")
ext := filepath.Ext("/var/log/app.log")
fmt.Println(dir, base, ext)

这些函数比手写字符串切割可靠。

path 用于 URL 路径

path 包总是使用 /,适合 URL 或嵌入式资源路径:

urlPath := path.Join("/api/", "/users/", "1001")
fmt.Println(urlPath) // /api/users/1001

不要用 filepath.Join 拼 URL。在 Windows 上它可能产生反斜杠,这不是 URL 路径。

简单规则:

本地文件系统路径 -> path/filepath
URL 路径、资源路径 -> path

这个区别在 Web 服务里很重要。读取本地文件用 filepath,生成链接用 path

遍历目录

func ListMarkdown(root string) ([]string, error) {
	var files []string

	err := filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		if filepath.Ext(p) == ".md" {
			files = append(files, p)
		}
		return nil
	})
	if err != nil {
		return nil, fmt.Errorf("walk %s: %w", root, err)
	}

	return files, nil
}

filepath.Walk 会递归遍历目录。回调里的 err 表示访问当前路径时发生的错误,要先处理。

如果你只需要读取一个目录,不递归:

entries, err := os.ReadDir(root)
if err != nil {
	return err
}

for _, entry := range entries {
	if entry.IsDir() {
		continue
	}
	fmt.Println(entry.Name())
}

选择递归还是单层读取,要看需求。

防止路径逃逸

处理用户传来的文件名时要小心:

filename := r.URL.Query().Get("file")
fullPath := filepath.Join(root, filename)

如果用户传 ../../etc/passwd,可能逃出根目录。需要检查:

func SafeJoin(root, name string) (string, error) {
	cleanRoot, err := filepath.Abs(root)
	if err != nil {
		return "", err
	}

	fullPath, err := filepath.Abs(filepath.Join(cleanRoot, name))
	if err != nil {
		return "", err
	}

	rel, err := filepath.Rel(cleanRoot, fullPath)
	if err != nil {
		return "", err
	}
	if strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
		return "", fmt.Errorf("path escapes root")
	}

	return fullPath, nil
}

上传、下载、静态文件和模板选择都要注意这个问题。不要直接相信用户传来的路径。

小结

Go 里本地文件路径用 path/filepath,URL 和斜杠资源路径用 path。拼接路径用 Join,清理用 Clean,取文件名和扩展名用 BaseDirExt,遍历目录可以用 filepath.Walkos.ReadDir

路径处理看起来只是字符串操作,但跨平台和安全边界都藏在里面。不要手写分隔符,不要把用户输入直接拼成文件路径。把这些基础做好,文件工具和 Web 服务都会稳很多。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页