
Go 的 os.Stat 返回的文件名总是与传入路径一致,无法反映磁盘上实际的大小写形式;要获取真实大小写名称,需遍历父目录并匹配文件的 inode 或唯一标识(如 os.FileInfo.Sys() 中的底层句柄),再提取其原始目录项名称。
go 的 `os.stat` 返回的文件名总是与传入路径一致,无法反映磁盘上实际的大小写形式;要获取真实大小写名称,需遍历父目录并匹配文件的 inode 或唯一标识(如 `os.fileinfo.sys()` 中的底层句柄),再提取其原始目录项名称。
在构建 lint 工具(如校验资源文件名是否全小写)时,仅靠 os.Stat("path.PNG") 得到的 f.Name() 值恒为 "PNG",而非磁盘上实际存储的 "png" 或 "PnG" —— 这是因为 os.FileInfo.Name() 语义上返回的是“调用者提供的 basename”,而非文件系统中的真实名称。
根本原因在于:
- 在类 Unix 系统中,文件本身没有“名字”,只有 inode;目录项(dirent)才保存名称,且同一 inode 可通过多个大小写不同的硬链接被访问;
- Windows 虽有大小写保留但不区分,API(如 GetFileAttributesEx)也不返回原始大小写名称;
- os.Stat 底层调用的 stat(2) 或 GetFileAttributesEx 均不携带文件名字段,故 FileInfo.Name() 只能回传输入参数。
✅ 正确做法:打开父目录,遍历其所有条目,通过 os.SameFile 或底层标识(如 inode / file ID)匹配目标文件,从而提取真实大小写名称。
以下是一个跨平台兼容的实现示例:
package main
import (
"os"
"path/filepath"
"runtime"
)
// RealFilename returns the case-exact basename of file as stored on disk.
// Returns error if file does not exist, or parent dir cannot be read.
func RealFilename(path string) (string, error) {
dir := filepath.Dir(path)
base := filepath.Base(path)
// Stat the target file to get its identity
target, err := os.Stat(path)
if err != nil {
return "", err
}
// Read all entries in parent directory
entries, err := os.ReadDir(dir)
if err != nil {
return "", err
}
// Match by identity: use os.SameFile for portability
for _, ent := range entries {
entPath := filepath.Join(dir, ent.Name())
if ent.IsDir() == target.IsDir() {
if same, _ := os.SameFile(ent, target); same {
return ent.Name(), nil
}
}
}
return "", os.ErrNotExist
}
// 使用示例
func main() {
name, err := RealFilename("assets/ICON.PNG")
if err != nil {
panic(err)
}
println("Actual name on disk:", name) // e.g., "icon.png" or "ICON.PNG"
}⚠️ 注意事项:
- os.SameFile 在大多数场景下足够可靠(基于 inode 或 Windows 文件 ID),但对符号链接需谨慎:若目标是 symlink,os.SameFile 比较的是目标文件而非链接本身;如需严格匹配 symlink 名称,请改用 ent.Name() 直接比对(但失去大小写无关性)。
- os.ReadDir(Go 1.16+)替代了已弃用的 ioutil.ReadDir,性能更好且内存更可控。
- 若路径不存在或无权限读取父目录,函数将返回错误,调用方应妥善处理。
- 该方法无法解决 NTFS 卷上启用“大小写敏感”选项(Windows 10+/WSL2)以外的 case-insensitive 文件系统(如默认 macOS HFS+/APFS、Windows NTFS)的“隐藏重命名”问题——此时即使文件名为 FILE.TXT,os.Stat("file.txt") 仍会成功,但 RealFilename 将返回 FILE.TXT,符合预期。
总结:os.Stat 不提供真实大小写名称是设计使然,而非 bug。依赖目录枚举 + 文件身份比对,是获取磁盘真实文件名的唯一可移植方案。在 lint 规则中,应始终用 RealFilename 替代 filepath.Base,确保校验逻辑与文件系统实际状态一致。


















