You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Go语言获取目录磁盘占用时计算结果异常的问题

Go获取目录实际磁盘占用大小问题排查与解决

问题说明

尝试用Go获取目录的实际磁盘占用,编写的程序返回结果全部为90GB,与du -sh的实际结果不符。不想使用filepath.Walk,之前尝试os.Stat()也无法得到目录内容的总大小。

现有代码

const (
    B  = 1
    KB = 1024 * B
    MB = 1024 * KB
    GB = 1024 * MB
)

type DiskStatus struct {
    All  uint64
    Free uint64
    Used uint64
}

func DiskUsage(path string) (ds DiskStatus) {
    fs := syscall.Statfs_t{}
    err := syscall.Statfs(path, &fs)
    if err != nil {
        return
    }
    ds.All = fs.Blocks * uint64(fs.Bsize)
    ds.Free = fs.Bfree * uint64(fs.Bsize)
    ds.Used = ds.All - ds.Free
    fmt.Println(path, ds.Used)
    return ds
}

func GetDir(destination_directory string) *[]model.Directory {
    var dir []model.Directory
    items, err := ioutil.ReadDir(destination_directory)

    if err != nil {
        log.Fatalln(err)
    }

    for _, item := range items {
        size := DiskUsage(destination_directory+item.Name()).Used / GB
        item := model.Directory{Path: item.Name(), TotalSize: size}
        dir = append(dir, item)
    }
    return &dir
}

实际目录大小(du -sh *输出)

8.0K containers
2.0G testfolder
3.2M workspace

程序运行结果

containers --> 90
testfolder --> 90
workspace --> 90

错误原因

syscall.Statfs()获取的是**整个文件系统(挂载点)**的磁盘使用情况,而非单个目录的内容占用。只要传入的路径属于同一个挂载分区,返回的Used值都是整个分区的已用空间,这就是所有目录结果一致的原因。

而os.Stat()返回的是目录自身的元数据大小(比如存储目录项的空间),不是目录下所有文件和子目录的总大小,因此也无法满足需求。

解决方案

根据需求提供两种可行方案:

方案1:调用系统du命令(推荐,与du结果一致)

利用系统自带的du工具计算目录大小,它会自动处理递归、磁盘块占用、符号链接等场景,结果和用户预期完全一致。适合Linux/macOS环境:

import (
    "bytes"
    "fmt"
    "log"
    "os"
    "os/exec"
    "strconv"
    "strings"
    "path/filepath"
)

const (
    B  = 1
    KB = 1024 * B
    MB = 1024 * KB
    GB = 1024 * MB
)

type Directory struct {
    Path      string
    TotalSize uint64
}

// GetDirSize 调用du命令获取目录实际磁盘占用(字节)
func GetDirSize(path string) (uint64, error) {
    // -s:只返回总大小;-B1:以字节为单位输出
    cmd := exec.Command("du", "-s", "-B1", path)
    var out bytes.Buffer
    cmd.Stdout = &out
    if err := cmd.Run(); err != nil {
        return 0, err
    }
    parts := strings.Fields(out.String())
    if len(parts) < 1 {
        return 0, fmt.Errorf("invalid du output for path: %s", path)
    }
    size, err := strconv.ParseUint(parts[0], 10, 64)
    if err != nil {
        return 0, err
    }
    return size, nil
}

func GetDir(destinationDirectory string) *[]Directory {
    var dirs []Directory
    items, err := os.ReadDir(destinationDirectory)
    if err != nil {
        log.Fatalln(err)
    }

    for _, item := range items {
        fullPath := filepath.Join(destinationDirectory, item.Name())
        sizeBytes, err := GetDirSize(fullPath)
        if err != nil {
            log.Printf("skip %s: failed to get size: %v", item.Name(), err)
            continue
        }
        dirs = append(dirs, Directory{
            Path:      item.Name(),
            TotalSize: sizeBytes / GB,
        })
    }
    return &dirs
}

方案2:纯Go递归遍历计算(跨平台)

自己实现目录递归遍历,计算所有文件的大小总和。注意:该方案计算的是文件实际字节数,和du结果可能有差异(du计算的是磁盘块占用,小文件会占满一个块),如果需要对齐du结果,还需结合文件系统块大小计算:

import (
    "log"
    "os"
    "path/filepath"
)

const (
    B  = 1
    KB = 1024 * B
    MB = 1024 * KB
    GB = 1024 * MB
)

type Directory struct {
    Path      string
    TotalSize uint64
}

// CalculateDirSize 递归计算目录下所有文件的字节总和
func CalculateDirSize(path string) (uint64, error) {
    info, err := os.Stat(path)
    if err != nil {
        return 0, err
    }
    if !info.IsDir() {
        return uint64(info.Size()), nil
    }

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

    var total uint64
    for _, entry := range entries {
        fullPath := filepath.Join(path, entry.Name())
        entryInfo, err := entry.Info()
        if err != nil {
            return 0, err
        }
        if entryInfo.IsDir() {
            subSize, err := CalculateDirSize(fullPath)
            if err != nil {
                return 0, err
            }
            total += subSize
        } else {
            total += uint64(entryInfo.Size())
        }
    }
    return total, nil
}

func GetDir(destinationDirectory string) *[]Directory {
    var dirs []Directory
    items, err := os.ReadDir(destinationDirectory)
    if err != nil {
        log.Fatalln(err)
    }

    for _, item := range items {
        fullPath := filepath.Join(destinationDirectory, item.Name())
        sizeBytes, err := CalculateDirSize(fullPath)
        if err != nil {
            log.Printf("skip %s: failed to get size: %v", item.Name(), err)
            continue
        }
        dirs = append(dirs, Directory{
            Path:      item.Name(),
            TotalSize: sizeBytes / GB,
        })
    }
    return &dirs
}

注意事项

  1. 路径拼接使用filepath.Join,避免手动拼接导致的跨平台路径错误。
  2. ioutil.ReadDir已废弃,改用os.ReadDir。
  3. 方案1依赖系统命令,Windows环境需要替换为dir或其他等效工具。

内容的提问来源于stack exchange,提问作者siwm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 03:32:07