路径问题经常在换系统后暴露
很多 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,取文件名和扩展名用 Base、Dir、Ext,遍历目录可以用 filepath.Walk 或 os.ReadDir。
路径处理看起来只是字符串操作,但跨平台和安全边界都藏在里面。不要手写分隔符,不要把用户输入直接拼成文件路径。把这些基础做好,文件工具和 Web 服务都会稳很多。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。