Saki's 研究记录

golang parse df -h output hard way

字数统计: 546阅读时长: 2 min
2021/12/05

背景

最近有个场景,需要解析远程调用命令df -h,输出的格式类似CentOS下的输出:

1
2
3
4
5
6
7
8
9
# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 99G 5.5G 89G 6% /
devtmpfs 3.8G 0 3.8G 0% /dev
tmpfs 3.8G 24K 3.8G 1% /dev/shm
tmpfs 3.8G 274M 3.6G 8% /run
tmpfs 3.8G 0 3.8G 0% /sys/fs/cgroup
tmpfs 778M 0 778M 0% /run/user/0
/dev/vdb 296G 485M 280G 1% /data

其中 -h的意思是:

-h, –human-readable print sizes in powers of 1024 (e.g. 1023M)

输出中各列的含义:

  • Filesystem:文件系统名,可解理为分区,包含物理和虚拟分区。
    • 其中dev表示device,即挂载的真实物理磁盘。
    • tmpfs为系统进程比如/run运行的临时分区。
  • Size:分区大小
  • Used:已使用容量
  • Avail:还可以使用的容量
  • Use%:已用百分比
  • Mounted on:挂载点(目录或路径)

代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package df

import (
"fmt"
"reflect"
"strings"
)

type commandOutputdrowIdx int

const (
command_output_row_idx_file_system commandOutputdrowIdx = iota
command_output_row_idx_size
command_output_row_idx_used
command_output_row_idx_available
command_output_row_idx_used_percent
command_output_row_idx_mounted_on
)

// headers is the headers in 'df -h' output.
var headers = []string{
"Filesystem",
"Size",
"Used",
"Avail",
"Use%",
// Mounted on
"Mounted",
"on",
}

// Parse parses 'df -h' command output and returns the rows.
func Parse(output string) ([]Row, error) {
lines := strings.Split(output, "\n")
rows := make([]Row, 0, len(lines))
headerFound := false
for _, line := range lines {
if len(line) == 0 {
continue
}

ds := strings.Fields(strings.TrimSpace(line))
if ds[0] == headers[0] {
if !reflect.DeepEqual(ds, headers) {
return nil, fmt.Errorf(`unexpected 'df -h' command header order (%v, expected %v, output: %q)`, ds, headers, output)
}
headerFound = true
continue
}

if !headerFound {
continue
}

row, err := parseRow(ds)
if err != nil {
return nil, err
}

rows = append(rows, row)
}

return rows, nil
}

func parseRow(row []string) (Row, error) {
if len(row) != len(headers)-1 {
return Row{}, fmt.Errorf(`unexpected row column number %v (expected %v)`, row, headers)
}

return Row{
Filesystem: strings.TrimSpace(row[command_output_row_idx_file_system]),
Size: strings.TrimSpace(row[command_output_row_idx_size]),
Used: strings.TrimSpace(row[command_output_row_idx_used]),
Avail: strings.TrimSpace(row[command_output_row_idx_available]),
UsePercent: strings.TrimSpace(strings.Replace(row[command_output_row_idx_used_percent], "%", " %", -1)),
MountedOn: strings.TrimSpace(row[command_output_row_idx_mounted_on]),
}, nil
}

代码中比较重要的地方

  • 比较列的内容和顺序是否符合预期
    1
    if !reflect.DeepEqual(ds, headers) { ... }
  • 解析行内容时,先判断是否是列的长度-1,因为列的Mounted on字段拆分后会算作两个字段
    1
    if len(row) != len(headers)-1 { ... }

Refrence

CATALOG
  1. 1. 背景
  2. 2. 代码片段
  3. 3. 代码中比较重要的地方
  4. 4. Refrence