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

 更新时间:2024年04月12日 10:08:51   作者:Meepoljd  
这篇文章主要介绍了使用Go实现在命令行输出好看的表格方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 浅谈go中cgo的几种使用方式

    浅谈go中cgo的几种使用方式

    本文主要介绍了浅谈go中cgo的几种使用方式,文中根据实例编码详细介绍的十分详尽,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • Go 语言 net/http 包使用之HTTP 服务器、客户端与中间件详解

    Go 语言 net/http 包使用之HTTP 服务器、客户端与中间件详解

    Go 语言标准库中的net/http包十分的优秀,提供了非常完善的 HTTP 客户端与服务端的实现,仅通过几行代码就可以搭建一个非常简单的 HTTP 服务器,本文给大家介绍Go语言net/http包使用之HTTP服务器、客户端与中间件的操作,感兴趣的朋友一起看看吧
    2025-05-05
  • go语言读取json并下载高清妹子图片

    go语言读取json并下载高清妹子图片

    前面我们介绍了使用python下载高清妹子图,作为程序猿,我们当然不能只会一种语言,今天我们就来使用go语言来读取API来下载妹子图吧,有需要的宅男们可以参考下。
    2015-03-03
  • 基于Go编写一个Windows剪贴板监控器

    基于Go编写一个Windows剪贴板监控器

    这篇文章主要为大家详细介绍了如何基于Go编写一个Windows剪贴板监控器,可以在后台默默监听你的复制行为,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-11-11
  • golang 实现Location跳转方式

    golang 实现Location跳转方式

    这篇文章主要介绍了golang 实现Location跳转方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • Golang Fasthttp选择使用slice而非map 存储请求数据原理探索

    Golang Fasthttp选择使用slice而非map 存储请求数据原理探索

    本文将从简单到复杂,逐步剖析为什么 Fasthttp 选择使用 slice 而非 map,并通过代码示例解释这一选择背后高性能的原因,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-02-02
  • Go字符串切片操作str1[:index]的使用

    Go字符串切片操作str1[:index]的使用

    Go字符串切片str1[:index]从起始位置0到index-1截取,不复制数据,利用字符串不可变性和共享内存机制提升性能,具有一定的参考价值,感兴趣的可以了解一下
    2025-06-06
  • Go语言轻量级线程Goroutine用法实例

    Go语言轻量级线程Goroutine用法实例

    这篇文章主要介绍了Go语言轻量级线程Goroutine用法,实例分析了goroutine使用技巧,需要的朋友可以参考下
    2015-02-02
  • Go语言中序列化与反序列化示例详解

    Go语言中序列化与反序列化示例详解

    我们的数据对象要在网络中传输或保存到文件,就需要对其编码和解码动作,Go语言当然也支持所有这些编码格式,下面这篇文章主要给大家介绍了关于Go语言中序列化与反序列化的相关资料,需要的朋友可以参考下
    2022-07-07
  • golang http 连接超时和传输超时的例子

    golang http 连接超时和传输超时的例子

    今天小编就为大家分享一篇golang http 连接超时和传输超时的例子,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-07-07

最新评论