开发者

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

开发者 https://www.devze.com 2024-08-14 14:27 出处:网络 作者: Meepoljd
目录生成Table表头设置插入行表格标题自动标号单元格合并列合并行合并样式设置居中设置数字自动高亮标红完整Demo代码结语最近在写一些运维小工具,比如批量进行ping包的工具,实现不困难,反正就是ping,统计,然后输
目录
  • 生成Table
  • 表头设置
  • 插入行
  • 表格标题
  • 自动标号
  • 单元格合并
    • 列合并
    • 行合并
  • 样式设置
    • 居中设置
    • 数字自动高亮标红
  • 完整Demo代码
    • 结语

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

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

      所以就去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 | ApandroidpendRows |
      +---+----------+-------+-------------+------------+------------+
      |   | 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 | PAjavascriptCKETSRECV | 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() {
      	columjsn := []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.Aliwww.devze.comgnCenter,
      		})
      	}
      	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,
      		},
      	})
      }
      

      实际效果如下:

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

      完整Demo代码

      package main
      
      import (
      	"fmt"
      	"math/rand"
      
      	"github.com/jedib0t/go-pretty/v6/table"
      	"github.com/jedib0t/go-pretjavascriptty/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库中还包含了进度条、列表等美化方法,感兴趣可以自己看看官方文档。

      以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号