当前位置: 代码网 > it编程>前端脚本>Golang > 使用Go实现在命令行输出好看的表格

使用Go实现在命令行输出好看的表格

2024年05月26日 Golang 我要评论
最近在写一些运维小工具,比如批量进行ping包的工具,实现不困难,反正就是ping,统计,然后输出,不过我本着自己既是开发者又是使用者的理念,还是不喜欢输出特别难看的工具,就像这样:所以就去https

最近在写一些运维小工具,比如批量进行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库中还包含了进度条、列表等美化方法,感兴趣可以自己看看官方文档。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com