diskusage
diskusage 是 显示磁盘使用情况的工具。同时支持(Linux、macOS 和 Windows)三大系统。
虽然说现在有很多的磁盘使用情况的工具,diskusage 还是有他的独特之处,他使用的是go语言进行编写,代码量非常的小,非常适合我们的go语言的初学着,等下我们也会对主要的代码进行分析,清楚的知道这款工具的逻辑。

安装
diskusage 的安装也比较简单了,使用传统的go install 就能轻松的把diskusage 安装到本地。
go install github.com/chenquan/diskusage@latest
使用
先看看使用帮助,来显示出diskusage 所支持的命令和diskusage 的功能。diskusage -h
$ diskusage -h
A tool for showing disk usage.
Usage:
diskusage [flags]
Flags:
-a, --all display all directories, otherwise only display folders whose usage size is not 0
-c, --color string set color output mode. optional: auto, always, ignore (default "auto")
-d, --depth int shows the depth of the tree directory structure (default 1)
--dir string dir path (default "./")
-f, --filter string regular expression filter (default ".+")
-h, --help help for diskusage
-t, --type strings only count certain types of files (default all)
-u, --unit string displayed units. optional: B(Bytes), K(KB), M(MB), G(GB), T(TB) (default "M")
最常见的一个功能就是列出当前目录的占用情况,经常我们在使用 linux的过程中,会产生一些文件,使得我们的磁盘爆满,但是我们又不知道占用最大空间的文件它在哪里,可以使用diskusage -a的命令来显示目录的占用情况。
diskusage -a
显示当前目录的情况如下:
Total: 3114.888M /root
---------------------------------
1692.0M 54.3% ┌─ snap
1284.5M 41.2% ├─ .cache
100.2M 3.2% ├─ .EasyOCR
31.0M 1.0% ├─ go
4.3M 0.1% ├─ flutter-webrtc-demo
2.1M 0.1% ├─ .launchpadlib
0.4M 0.0% ├─ 1yw32lok3j51yw32lok3j5.jpg
0.2M 0.0% ├─ 1yw32lok3j51yw32lok3j5-1.jpg
54.1K 0.0% ├─ 4f70c06988d732947b62c18501a876a8-1.jpeg
43.6K 0.0% ├─ 4f70c06988d732947b62c18501a876a8.jpeg
22.5K 0.0% ├─ Dingtalk_20220804094819.jpg
14.7K 0.0% ├─ .viminfo
3.7K 0.0% ├─ .bashrc
0.6K 0.0% ├─ .bash_history
0.5K 0.0% ├─ scene.py
0.2K 0.0% ├─ .python_history
0.2K 0.0% ├─ .profile
0.1K 0.0% ├─ .config
78.0B 0.0% ├─ .flutter
0.0B 0.0% ├─ .jina
0.0B 0.0% └─ .sudo_as_admin_successful
这个就非常清楚的展示了目录的情况,当前目录大小3114.888M,snap目录占了1692.0M 占了 54.3%,目录的情况一目了然,还怕找不到相关的文件吗?
代码分析
diskusage 的文件结构也比较简单,最重要的文件为stat.go,有几个函数比较关键,接下来一个一个分析下,也比较简单,相当于教我们怎么 统计文件夹的情况。

主函数在这里,看起来逻辑也挺正常的,先算总的文件大小,在一个一个的去遍历:
func Stat(cmd *cobra.Command, _ []string) error {
flags := cmd.Flags()
dir, err := flags.GetString("dir")
if err != nil {
return err
}
depth, err := flags.GetInt("depth")
if err != nil {
return err
}
unit, err := getUnit(flags)
if err != nil {
return err
}
dir, err = filepath.Abs(dir)
if err != nil {
return err
}
types, err := flags.GetStringSlice("type")
if err != nil {
return err
}
typeMap := make(map[string]struct{}, len(types))
for _, s := range types {
typeMap["."+s] = struct{}{}
}
filter, err := flags.GetString("filter")
if err != nil {
return err
}
compile, err := regexp.Compile(filter)
if err != nil {
return err
}
all, err := flags.GetBool("all")
if err != nil {
return err
}
err = handleColor(flags)
if err != nil {
return err
}
go func() {
defer close(errChan)
files, err := find(dir, func(info fs.FileInfo) bool {
if info.IsDir() {
return true
}
name := info.Name()
ext := filepath.Ext(name)
_, ok := typeMap[ext]
typeB := ok || len(types) == 0
filterB := compile.MatchString(name)
return typeB && filterB
})
if err != nil {
errChan <- err
return
}
totalSize := int64(0)
for _, f := range files {
totalSize += f.size
}
val, reduceUnit := getReduce(unit, totalSize)
header := fmt.Sprintf("Total: %0.3f%s\t%s", val, reduceUnit, color.HiGreenString(dir))
colorPrintln(header)
colorPrintln(strings.Repeat("-", len(header)+2))
l := list.NewWriter()
l.SetStyle(list.StyleConnectedLight)
infoFiles := buildInfoFile(l, files, 0, depth, unit, totalSize, all)
maxLen := 0
for _, info := range infoFiles {
if maxLen < len(info.str) {
maxLen = len(info.str)
}
}
printTree(l.Render(), infoFiles, maxLen)
errChan <- nil
}()
if <-errChan != nil {
return err
}
return nil
}
文件大小格式化:
func getReduce(unit string, n int64) (float64, string) {
reduce := 0
switch unit {
case "B":
reduce = 0
case "K":
reduce = 1
case "M":
reduce = 2
case "G":
reduce = 3
case "T":
reduce = 4
}
for {
if reduce <= 0 {
break
}
if int(float64(n)/float64(units[reduce])*10) > 0 {
break
}
reduce--
}
return float64(n) / float64(units[reduce]), unitStrings[reduce]
}
查找文件:
func find(dir string, filter func(info fs.FileInfo) bool) ([]file, error) {
if !sysFilter(dir) {
return nil, nil
}
dirEntries, err := os.ReadDir(dir)
if err != nil {
if pathError, ok := err.(*fs.PathError); ok {
// currently only supports Windows
if accessDeniedSyscall(pathError.Err) {
return nil, errorAccessDenied
}
}
return nil, err
}
var wg = sync.WaitGroup{}
fileChan := make(chan file, len(dirEntries))
for _, entry := range dirEntries {
entry := entry
fileInfo, err := entry.Info()
if err != nil {
return nil, err
}
if !filter(fileInfo) {
continue
}
if !entry.IsDir() {
fileChan <- file{
name: entry.Name(),
size: fileInfo.Size(),
}
continue
}
wg.Add(1)
do := func() {
defer wg.Done()
subFiles, err := find(path.Join(dir, entry.Name()), filter)
if err != nil {
if accessDenied(err) {
return
}
errChan <- err
}
totalSize := int64(0)
for _, subFile := range subFiles {
totalSize += subFile.size
}
fileChan <- file{
sub: subFiles,
name: entry.Name(),
isDir: true,
size: totalSize,
}
}
w.Run(do)
}
wg.Wait()
close(fileChan)
files := make([]file, 0, len(dirEntries))
for f := range fileChan {
files = append(files, f)
}
sort.Slice(files, func(i, j int) bool { return files[i].size > files[j].size })
return files, nil
}
递归遍历文件信息:
func buildInfoFile(l list.Writer, files []file, n, depth int, unit string, totalSize int64, all bool) []infoFile {
if n == depth {
return nil
}
var infoFiles []infoFile
for _, f := range files {
if f.isDir && f.size == 0 && !all {
continue
}
val, reduceUnit := getReduce(unit, f.size)
infoFiles = append(infoFiles, infoFile{
size: val,
uint: reduceUnit,
usageRate: float64(f.size) / float64(totalSize) * 100,
str: fmt.Sprintf("%0.1f", val),
isDir: f.isDir,
})
name := f.name
if f.isDir {
name = color.HiGreenString(name)
}
l.AppendItem(name)
if f.isDir {
l.Indent()
subUsageSizes := buildInfoFile(l, f.sub, n+1, depth, unit, totalSize, all)
infoFiles = append(infoFiles, subUsageSizes...)
l.UnIndent()
}
}
return infoFiles
}
把结果显示出来:
func printTree(content string, infoFiles []infoFile, maxLen int) {
size := len(infoFiles)
format := " %" + strconv.Itoa(maxLen) + ".1f%s %5.1f%%"
for i, line := range strings.Split(content, "\n") {
if i >= size {
continue
}
info := infoFiles[i]
str := fmt.Sprintf(format, info.size, info.uint, info.usageRate)
if info.isDir {
str = color.HiRedString(str)
}
colorPrintln(str, line)
}
colorPrintln()
}
本文含有隐藏内容,请 开通VIP 后查看