os.fileinfo 不含 uid/gid 因其是跨平台抽象,需类型断言到 syscall.stat_t(unix)或 syscall.win32fileattributedata(windows)获取系统特有字段,且须用 ok 判断并按 os 分支处理。

Go 里 os.Stat 返回的 os.FileInfo 为什么看不到 uid/gid?
因为 os.FileInfo 是跨平台抽象接口,只保留通用字段(如大小、修改时间、权限),系统级元数据(如 uid、gid、inode、device)被刻意隐藏了。想拿到这些,必须类型断言到具体实现——在 Unix 系统上是 syscall.Stat_t,Windows 上是 syscall.Win32FileAttributeData。
常见错误现象:fi.Sys().(*syscall.Stat_t) panic,原因是没检查 fi.Sys() 是否为 *syscall.Stat_t;或在 Windows 下硬转 Unix 类型,直接崩溃。
- Unix/Linux/macOS:用
fi.Sys().(*syscall.Stat_t)获取完整 stat 结构 - Windows:用
fi.Sys().(*syscall.Win32FileAttributeData),但注意它不提供 uid/gid(Windows 没有传统 uid 概念) - 跨平台代码必须先做类型判断,不能假设
fi.Sys()一定可转
os.Lstat 和 os.Stat 在符号链接场景下行为差异
区别不在“是否跟随链接”,而在于“是否解析链接目标”:os.Stat 读取的是链接指向的文件属性;os.Lstat 读取的是链接文件自身属性(比如它的 owner、size、mtime 都属于这个 .lnk 文件,不是它指向的目标)。
典型误用:用 os.Stat 检查一个符号链接是否存在时,如果目标被删了,会报 no such file or directory;而 os.Lstat 能成功返回链接自身的元数据。
立即学习“go语言免费学习笔记(深入)”;
- 要判断路径是否为符号链接:用
fi.Mode()&os.ModeSymlink != 0,且必须基于os.Lstat的结果 - 要获取链接目标的真实 uid/gid:先
os.Lstat确认是 link,再os.Stat获取目标信息 - 性能影响:两次系统调用比一次多,但避免了竞态(link 目标可能在两次调用间被改/删)
从 syscall.Stat_t 提取 uid/gid 时要注意字段名和平台差异
Linux 和 macOS 的 syscall.Stat_t 结构体字段名一致(Uid、Gid),但字段类型不同:Linux 是 uint32,macOS 是 uint32(看着一样,但实际定义来自不同头文件,不能混用头文件)。更关键的是:Go 的 syscall 包在 Go 1.19+ 已开始弃用,推荐迁移到 golang.org/x/sys/unix。
错误示例:import "syscall"; s := fi.Sys().(*syscall.Stat_t); fmt.Println(s.Uid) —— 在 Go 1.20+ 编译失败,因 syscall 不再导出 Stat_t。
- 新写法:导入
golang.org/x/sys/unix,用unix.Stat_t替代syscall.Stat_t - 字段访问不变:
st.Uid、st.Gid、st.Ino、st.Dev - 注意:
unix.Stat函数需传入*unix.Stat_t,不是*syscall.Stat_t,类型不兼容
为什么 os.FileInfo.Size() 和 syscall.Stat_t.Size 有时不一致?
不会不一致。os.FileInfo.Size() 就是把底层 syscall.Stat_t.Size(或等价字段)封装后返回的。如果你看到差异,大概率是用了 os.Lstat + 符号链接:链接文件本身的 size 是路径字符串长度(比如 "../foo" 是 7),而 os.FileInfo.Size() 返回的就是这个值;但人误以为它该返回目标文件大小。
另一个干扰项:设备文件(如 /dev/sda)在某些系统上 Size() 返回 0,但 st.Size 也是 0 —— 这是内核行为,不是 Go 的 bug。
- 普通文件/目录:两者恒等
- 符号链接:两者都返回链接内容长度,不是目标大小
- 不要试图用
Size()判断是否为目录——用fi.IsDir()或fi.Mode().IsDir()
fi.Sys() 的类型断言必须带 ok 判断,而且得按运行时 OS 分支处理;不是所有系统都有 uid/gid,也不是所有 Go 版本都让 syscall.Stat_t 可见。










