
本文介绍如何通过解析 /proc 文件系统,在 Go 程序中安全、高效地枚举当前用户(UID)所有打开的文件描述符,适用于日志清理前的占用检测等运维场景。
本文介绍如何通过解析 `/proc` 文件系统,在 go 程序中安全、高效地枚举当前用户(uid)所有打开的文件描述符,适用于日志清理前的占用检测等运维场景。
在 Linux 系统中,当需要自动清理日志文件(如按大小配额触发删除)时,一个关键安全步骤是:确认目标文件未被任何进程以读写方式打开,否则直接 os.Remove() 可能导致数据丢失或应用异常(尽管内核会保留已打开文件的 inode,但新写入将不可见,且部分服务可能因文件消失而崩溃)。
Go 语言本身不提供跨平台的“获取用户所有打开文件”API,但可借助 Linux 特有的 /proc 虚拟文件系统实现。其核心思路是:
- 获取当前进程的有效用户 ID(
os.Geteuid()); - 遍历
/proc/[0-9]+/中所有数字命名的进程目录; - 对每个进程,检查其
/proc/pid/status中的Uid:字段,匹配目标 UID; - 若匹配成功,读取
/proc/pid/fd/目录下的符号链接,解析其指向的实际文件路径(需filepath.EvalSymlinks或os.Readlink); - 去重并过滤掉非常规文件(如
socket:[...],anon_inode:[...],/dev/...等)。
以下是一个精简可靠的 Go 示例实现:
Zynq‑7000 / Zynq UltraScale+ 硬件调试技能。适用于 Xilinx Zynq(Cortex‑A9/A53 + FPGA)Linux 环境,调试 I2C、I2S、UART、GPIO 等外设。包括设备树配置、内核驱动、用户空间调试工具、交叉编译和常见问题排查,支持 PetaLinux 开发。
package main
import (
"fmt"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
)
func getOpenFilesByUser() ([]string, error) {
usr, err := user.Current()
if err != nil {
return nil, err
}
uid, err := strconv.ParseUint(usr.Uid, 10, 32)
if err != nil {
return nil, err
}
var files []string
procDir := "/proc"
dirs, _ := os.ReadDir(procDir)
for _, d := range dirs {
if !d.IsDir() || !strings.All(func(r rune) bool { return r >= '0' && r 1 {
if realUID, _ := strconv.ParseUint(parts[1], 10, 32); realUID == uid {
// 匹配成功,扫描其 fd
fdDir := filepath.Join(pidDir, "fd")
fds, _ := os.ReadDir(fdDir)
for _, fd := range fds {
linkPath := filepath.Join(fdDir, fd.Name())
target, err := os.Readlink(linkPath)
if err != nil || strings.HasPrefix(target, "socket:[") || strings.HasPrefix(target, "anon_inode:") || strings.HasPrefix(target, "/dev/") {
continue
}
if !strings.HasPrefix(target, "/") {
continue // 忽略相对路径(如 cwd、root)
}
absTarget, _ := filepath.EvalSymlinks(target)
if absTarget != "" {
files = append(files, absTarget)
}
}
}
}
break
}
}
}
return deduplicate(files), nil
}
func deduplicate(ss []string) []string {
seen := make(map[string]bool)
var result []string
for _, s := range ss {
if !seen[s] {
seen[s] = true
result = append(result, s)
}
}
return result
}
func main() {
files, err := getOpenFilesByUser()
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Found %d open files:\n", len(files))
for _, f := range files {
fmt.Println(f)
}
}
⚠️ 注意事项:
- 该方法依赖
/proc,仅适用于 Linux;macOS / Windows 需改用lsof -u $USER等外部命令(通过exec.Command调用); - 遍历
/proc需要足够权限(普通用户可读多数进程的fd/,但部分敏感进程可能拒绝访问); -
os.Readlink返回的路径可能是相对proc/pid/root的,生产环境建议结合/proc/pid/exe和proc/pid/cwd做更精确解析(本例为简洁性做了简化); -
更优实践推荐:对于日志管理,优先使用
logrotate配置copytruncate或create指令,并配合inotifywait监听文件变更,而非手动轮询删除 —— 这比实时检测打开文件更健壮、低开销。
综上,解析 /proc 是 Go 在 Linux 下获取用户级打开文件的底层可行方案,适合轻量级运维工具;但长期项目应转向成熟的日志生命周期管理方案(如 logrotate + systemd 日志配额),兼顾安全性与可维护性。










