最近在写一些运维小工具,比如批量进行ping包的工具,实现不困难,反正就是ping,统计,然后输出,不过我本着自己既是开发者又是使用者的理念,还是不喜欢输出特别难看的工具,就像这样:
所以就去https://pkg.go.dev/瞄了一眼,看看有没有啥适合的库能够把输出整的好看点的,于是找到了一个库github.com/jedib0t/go-pretty/v6/table
这是一个在命令行输出格式化表格的库,这里记录一下使用这个库进行一些格式化输出的过程。
其实还有一个比较简单的库叫做gotable,也能实现基础的格式化输出功能,使用起来也方便些,不过功能相对来说就要单一一些,在表格样式设置上会差一些,没那么自由
也可以看下https://pkg.go.dev/github.com/liushuochen/gotable#section-readme
接下来开始正式的去在命令行生成好看的满足需要的表格。
生成table
首先我们要生成一个table结构体的实例,可以直接new一个,也可以自己构造:
t := table.table{} // 或者 t := table.newwriter()
newwriter会返回一个writer接口
表头设置
表格首先要设置表头,以我的应用为例,表头设置:
header := table.row{"id", "ip", "num", "packetsrecv", "packetloss", "avgrtt"}
这样生成了一个表头行,然后要通过appendheader方法在表格中生效:
t.appendheader(header)
看看效果,表头已经打印出来了
+----+----+-----+-------------+------------+--------+ | id | ip | num | packetsrecv | packetloss | avgrtt | +----+----+-----+-------------+------------+--------+ +----+----+-----+-------------+------------+--------+
插入行
数据的插入和表头的生成类似,要生成一个table.row,然后调用appendrow方法:
func (d *demo) appendrow() { for i := 1; i <= 5; i++ { row := table.row{i, fmt.sprintf("10.0.0.%v", i), i + 4, i, i, "appendrow"} d.t.appendrow(row) } }
效果如下:
+----+----------+-----+-------------+------------+-----------+ | id | ip | num | packetsrecv | packetloss | avgrtt | +----+----------+-----+-------------+------------+-----------+ | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrow | | 2 | 10.0.0.2 | 6 | 2 | 2 | appendrow | | 3 | 10.0.0.3 | 7 | 3 | 3 | appendrow | | 4 | 10.0.0.4 | 8 | 4 | 4 | appendrow | | 5 | 10.0.0.5 | 9 | 5 | 5 | appendrow | +----+----------+-----+-------------+------------+-----------+
当然也可以生成table.row的切片后调用一次appendrows方法,效果和上面是一样的:
func (d *demo) appendrows() { var rows []table.row for i := 1; i <= 5; i++ { rows = append(rows, table.row{i, fmt.sprintf("10.0.0.%v", i), i + 4, i, i, "appendrows"}) } d.t.appendrows(rows) }
+----+----------+-----+-------------+------------+------------+ | id | ip | num | packetsrecv | packetloss | avgrtt | +----+----------+-----+-------------+------------+------------+ | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrow | | 2 | 10.0.0.2 | 6 | 2 | 2 | appendrow | | 3 | 10.0.0.3 | 7 | 3 | 3 | appendrow | | 4 | 10.0.0.4 | 8 | 4 | 4 | appendrow | | 5 | 10.0.0.5 | 9 | 5 | 5 | appendrow | | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrows | | 2 | 10.0.0.2 | 6 | 2 | 2 | appendrows | | 3 | 10.0.0.3 | 7 | 3 | 3 | appendrows | | 4 | 10.0.0.4 | 8 | 4 | 4 | appendrows | | 5 | 10.0.0.5 | 9 | 5 | 5 | appendrows | +----+----------+-----+-------------+------------+------------+
表格标题
在设置表格实际内容时,还可以设置一个表格标题,如下:
func (d *demo) addtitle() { d.t.settitle("this is easy table") }
+-------------------------------------------------------------+ | this is easy table | +----+----------+-----+-------------+------------+------------+ | id | ip | num | packetsrecv | packetloss | avgrtt | +----+----------+-----+-------------+------------+------------+ | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrow | | 2 | 10.0.0.2 | 6 | 2 | 2 | appendrow | | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrows | | 2 | 10.0.0.2 | 6 | 2 | 2 | appendrows | +----+----------+-----+-------------+------------+------------+
自动标号
在插入行的时候,我额外输入了一个id列,作为标号,其实table提供了相关的方法和接口,只需要调用setautoindex方法,增加自动的索引列即可:
func (d *demo) makeheader() { header := table.row{"ip", "num", "packetsrecv", "packetloss", "avgrtt"} d.t.appendheader(header) d.t.setautoindex(true) }
+------------------------------------------------------------+ | this is easy table | +---+----------+-----+-------------+------------+------------+ | | ip | num | packetsrecv | packetloss | avgrtt | +---+----------+-----+-------------+------------+------------+ | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrow | | 2 | 10.0.0.2 | 6 | 2 | 2 | appendrow | | 3 | 10.0.0.1 | 5 | 1 | 1 | appendrows | | 4 | 10.0.0.2 | 6 | 2 | 2 | appendrows | +---+----------+-----+-------------+------------+------------+
单元格合并
有的时候,相邻单元格的值一样我们可能会想要进行合并,这样更美观,单元格合并分为列合并和行合并;先定义一下这里的列合并和行合并:
- 列合并:针对单列,如果单列中的多个相邻行数据一样,那么就合并为一个大行;
- 行合并:针对单行,如果单行中的多个相邻列数据一样,那么久合并为一个大列;
这里我们用到的原始表格如下:
+--------------------------------------------------------------+ | this is easy table | +---+----------+-------+-------------+------------+------------+ | | ip | num | packetsrecv | packetloss | avgrtt | +---+----------+-------+-------------+------------+------------+ | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrow | | 2 | 10.0.0.2 | 6 | 2 | 2 | appendrow | | 3 | 10.0.0.1 | 5 | 1 | 1 | appendrows | | 4 | 10.0.0.2 | 6 | 2 | 2 | appendrows | +---+----------+-------+-------------+------------+------------+ | | total | total | total | total | 4 | +---+----------+-------+-------------+------------+------------+
列合并
我们先进行最后一列avgrtt的列合并:
func (d *demo) columnmerge() { d.t.setcolumnconfigs([]table.columnconfig{ { name: "avgrtt", // number是指定列的序号 // number: 5, automerge: true, align: text.aligncenter, }, }) }
可以选择通过列的表头或者列的序号来选择具体进行合并的列:
+---+----------+-------+-------------+------------+------------+ | | ip | num | packetsrecv | packetloss | avgrtt | +---+----------+-------+-------------+------------+------------+ | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrow | | 2 | 10.0.0.2 | 6 | 2 | 2 | | | 3 | 10.0.0.1 | 5 | 1 | 1 | appendrows | | 4 | 10.0.0.2 | 6 | 2 | 2 | | +---+----------+-------+-------------+------------+------------+ | | total | total | total | total | 4 | +---+----------+-------+-------------+------------+------------+
这样看表格线条不明显,感觉不到区分,那么可以加上一些设置d.t.style().options.separaterows = true
+---+----------+-------+-------------+------------+------------+ | | ip | num | packetsrecv | packetloss | avgrtt | +---+----------+-------+-------------+------------+------------+ | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrow | +---+----------+-------+-------------+------------+ | | 2 | 10.0.0.2 | 6 | 2 | 2 | | +---+----------+-------+-------------+------------+------------+ | 3 | 10.0.0.1 | 5 | 1 | 1 | appendrows | +---+----------+-------+-------------+------------+ | | 4 | 10.0.0.2 | 6 | 2 | 2 | | +---+----------+-------+-------------+------------+------------+ | | total | total | total | total | 4 | +---+----------+-------+-------------+------------+------------+
行合并
行合并我们对最后一行的汇总行进行合并,具体做法是在添加汇总行时增加rowconfig参数:
func (d *demo) appendfooter() { d.t.appendfooter(table.row{"total", "total", "total", "total", count}, table.rowconfig{automerge: true}) }
+---+----------+-------+-------------+------------+------------+ | | ip | num | packetsrecv | packetloss | avgrtt | +---+----------+-------+-------------+------------+------------+ | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrow | +---+----------+-------+-------------+------------+ | | 2 | 10.0.0.2 | 6 | 2 | 2 | | +---+----------+-------+-------------+------------+------------+ | 3 | 10.0.0.1 | 5 | 1 | 1 | appendrows | +---+----------+-------+-------------+------------+ | | 4 | 10.0.0.2 | 6 | 2 | 2 | | +---+----------+-------+-------------+------------+------------+ | | total | 4 | +---+---------------------------------------------+------------+
样式设置
现在整个表格已经生成,但我们还需要进行一些美化,这就要对表格的样式进行设置了;
居中设置
对于居中,无法直接进行全局的设置,必须根据列进行,如下:
func (d *demo) setaligncenter() { column := []string{"ip", "num", "packetsrecv", "packetloss", "avgrtt"} c := []table.columnconfig{} // 根据表格的列数循环进行设置,统一居中 for i := 1; i <= len(column); i++ { name := column[i-1] if name == "avgrtt" { c = append(c, table.columnconfig{ name: "avgrtt", automerge: true, align: text.aligncenter, alignheader: text.aligncenter, alignfooter: text.aligncenter, }) continue } c = append(c, table.columnconfig{ name: column[i], align: text.aligncenter, alignheader: text.aligncenter, alignfooter: text.aligncenter, }) } d.t.setcolumnconfigs(c) }
居中效果如下,这样既能保留列合并又完成了剧中设置:
+---+----------+-------+-------------+------------+------------+ | | ip | num | packetsrecv | packetloss | avgrtt | +---+----------+-------+-------------+------------+------------+ | 1 | 10.0.0.1 | 5 | 1 | 1 | appendrow | +---+----------+-------+-------------+------------+ | | 2 | 10.0.0.2 | 6 | 2 | 2 | | +---+----------+-------+-------------+------------+------------+ | 3 | 10.0.0.1 | 5 | 1 | 1 | appendrows | +---+----------+-------+-------------+------------+ | | 4 | 10.0.0.2 | 6 | 2 | 2 | | +---+----------+-------+-------------+------------+------------+ | | total | 4 | +---+---------------------------------------------+------------+
数字自动高亮标红
在我的应用场景中,ping的ip如果出现了丢包情况,那就要红色高亮,方便使用者马上关注到,这种情况下,可以通过transformer来设置:
func (d *demo) setwarncolor() { // 字体颜色 warncolor := text.colors{text.bgred} warntransformer := text.transformer(func(val interface{}) string { if val.(float64) > 0 { // 统计丢包服务器总数 return warncolor.sprintf("%.2f%%", val) } return fmt.sprintf("%v%%", val) }) d.t.setcolumnconfigs([]table.columnconfig{ { name: "packetloss", automerge: true, align: text.aligncenter, alignheader: text.aligncenter, alignfooter: text.aligncenter, transformer: warntransformer, }, }) }
实际效果如下:
完整demo代码
package main import ( "fmt" "math/rand" "github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/text" ) var count = 0 type demo struct { t table.writer } func newdemo() *demo { return &demo{ t: table.newwriter(), } } func (d *demo) makeheader() { header := table.row{"ip", "num", "packetsrecv", "packetloss", "avgrtt"} d.t.appendheader(header) d.t.setautoindex(true) // d.t.setstyle(table.stylelight) d.t.style().options.separaterows = true } func (d *demo) addtitle() { d.t.settitle("this is easy table") } func (d *demo) appendrow() { // rowconfig := table.rowconfig{automerge: true} for i := 1; i <= 2; i++ { row := table.row{fmt.sprintf("10.0.0.%v", i), i + 4, i, rand.float64() * 100, "appendrow"} count += 1 d.t.appendrow(row) } d.t.appendrow(table.row{fmt.sprintf("10.0.0.%v", 4), 1 + 4, 1, 0.0, "appendrow"}) } func (d *demo) appendrows() { var rows []table.row for i := 1; i <= 2; i++ { rows = append(rows, table.row{fmt.sprintf("10.0.0.%v", i), i + 4, i, rand.float64() * 100, "appendrows"}) count += 1 } d.t.appendrows(rows) } func (d *demo) appendfooter() { d.t.appendfooter(table.row{"total", "total", "total", "total", count}, table.rowconfig{automerge: true, automergealign: text.aligncenter}) } func (d *demo) columnmerge() { d.t.setcolumnconfigs([]table.columnconfig{ { name: "avgrtt", // number是指定列的序号 // number: 5, automerge: true, align: text.aligncenter, }, }) } func (d *demo) setaligncenter() { column := []string{"ip", "num", "packetsrecv", "packetloss", "avgrtt"} c := []table.columnconfig{} // 根据表格的列数循环进行设置,统一居中 for i := 1; i <= len(column); i++ { name := column[i-1] if name == "avgrtt" { c = append(c, table.columnconfig{ name: "avgrtt", automerge: true, align: text.aligncenter, alignheader: text.aligncenter, alignfooter: text.aligncenter, }) continue } c = append(c, table.columnconfig{ name: column[i], align: text.aligncenter, alignheader: text.aligncenter, alignfooter: text.aligncenter, }) } d.t.setcolumnconfigs(c) } func (d *demo) setwarncolor() { // 字体颜色 warncolor := text.colors{text.bgred} warntransformer := text.transformer(func(val interface{}) string { if val.(float64) > 0 { // 统计丢包服务器总数 return warncolor.sprintf("%.2f%%", val) } return fmt.sprintf("%v%%", val) }) d.t.setcolumnconfigs([]table.columnconfig{ { name: "packetloss", automerge: true, align: text.aligncenter, alignheader: text.aligncenter, alignfooter: text.aligncenter, transformer: warntransformer, }, }) } func (d *demo) print() { fmt.println(d.t.render()) } func main() { demo := newdemo() demo.makeheader() // demo.addtitle() demo.appendrow() demo.appendrows() // demo.columnmerge() demo.appendfooter() // demo.setaligncenter() demo.setwarncolor() demo.print() }
结语
本文介绍了使用第三方库美化golang的命令行表格格式化输出,除了table以外,go-pretty
库中还包含了进度条、列表等美化方法,感兴趣可以自己看看官方文档。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论