CSV 是一种非常简单的格式,如下所示:

1
2
3
4
ipv;table;command;chain;proto;src;dst;sport;dport;match;jump;desc
4,6;filter;A;x;tcp;;;;6789;;ACCEPT;MON
4,6;filter;A;x;tcp;;;;6800:7300;;ACCEPT;OSD,MGR,MDS
4,6;filter;A;x;tcp;;;;3300;;ACCEPT;MON >= Nautilus

但 golang 自带的 encoding/csv 包只是简单对字符串解析进行了一些封装,因此用起来算不上有多方便。

第三方的 gocsv 库 虽然实现的并不怎么样,例如没有对外暴露 Encoder/Decoder,没有暴露错误码定义,但用起来还算方便。

在使用之前需要进行必要的设置(不是很理解为何不暴露 Encoder/Decoder):

1
2
3
4
5
6
7
8
9
10
gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader {
reader := csv.NewReader(in)
reader.Comma = ';'
return reader
})
gocsv.SetCSVWriter(func(out io.Writer) *gocsv.SafeCSVWriter {
writer := csv.NewWriter(out)
writer.Comma = ';'
return gocsv.NewSafeCSVWriter(writer)
})

剩下的就非常简单了,首先定义结构体的各字段(注意结构体的 tag 需要与 CSV 文件的表头对应):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type RawRule struct {
Ipv string `csv:"ipv"`
Table string `csv:"table"`
Command string `csv:"command"`
Chain string `csv:"chain"`
Proto string `csv:"proto"`
Src string `csv:"src"`
Dst string `csv:"dst"`
Sport string `csv:"sport"`
Dport string `csv:"dport"`
Match string `csv:"match"`
Jump string `csv:"jump"`
Desc string `csv:"desc"`
}

然后直接 unmarshal 操作即可:

1
2
3
4
var rules []RawRule
if err := gocsv.UnmarshalString(data, &rules); err != nil {
return RawRules{}, errors.Wrap(err, "failed to parse rules")
}

而 marshal 也非常简单:

1
2
3
4
buffer := bytes.NewBuffer(nil)
if err := gocsv.Marshal(rules, buffer); err != nil {
return nil, errors.Wrapf(err, "failed to encode rules")
}