使用Go實(shí)現(xiàn)在命令行輸出好看的表格
最近在寫(xiě)一些運(yùn)維小工具,比如批量進(jìn)行ping包的工具,實(shí)現(xiàn)不困難,反正就是ping,統(tǒng)計(jì),然后輸出,不過(guò)我本著自己既是開(kāi)發(fā)者又是使用者的理念,還是不喜歡輸出特別難看的工具,就像這樣:

所以就去https://pkg.go.dev/瞄了一眼,看看有沒(méi)有啥適合的庫(kù)能夠把輸出整的好看點(diǎn)的,于是找到了一個(gè)庫(kù)github.com/jedib0t/go-pretty/v6/table
這是一個(gè)在命令行輸出格式化表格的庫(kù),這里記錄一下使用這個(gè)庫(kù)進(jìn)行一些格式化輸出的過(guò)程。
其實(shí)還有一個(gè)比較簡(jiǎn)單的庫(kù)叫做gotable,也能實(shí)現(xiàn)基礎(chǔ)的格式化輸出功能,使用起來(lái)也方便些,不過(guò)功能相對(duì)來(lái)說(shuō)就要單一一些,在表格樣式設(shè)置上會(huì)差一些,沒(méi)那么自由
也可以看下https://pkg.go.dev/github.com/liushuochen/gotable#section-readme
接下來(lái)開(kāi)始正式的去在命令行生成好看的滿足需要的表格。
生成Table
首先我們要生成一個(gè)Table結(jié)構(gòu)體的實(shí)例,可以直接New一個(gè),也可以自己構(gòu)造:
t := table.Table{}
// 或者
t := table.NewWriter()
NewWriter會(huì)返回一個(gè)Writer接口
表頭設(shè)置
表格首先要設(shè)置表頭,以我的應(yīng)用為例,表頭設(shè)置:
header := table.Row{"ID", "IP", "Num", "PacketsRecv", "PacketLoss", "AvgRtt"}這樣生成了一個(gè)表頭行,然后要通過(guò)AppendHeader方法在表格中生效:
t.AppendHeader(header)
看看效果,表頭已經(jīng)打印出來(lái)了
+----+----+-----+-------------+------------+--------+ | ID | IP | NUM | PACKETSRECV | PACKETLOSS | AVGRTT | +----+----+-----+-------------+------------+--------+ +----+----+-----+-------------+------------+--------+
插入行
數(shù)據(jù)的插入和表頭的生成類似,要生成一個(gè)table.Row,然后調(diào)用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 | +----+----------+-----+-------------+------------+-----------+
當(dāng)然也可以生成table.Row的切片后調(diào)用一次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 | +----+----------+-----+-------------+------------+------------+
表格標(biāo)題
在設(shè)置表格實(shí)際內(nèi)容時(shí),還可以設(shè)置一個(gè)表格標(biāo)題,如下:
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 | +----+----------+-----+-------------+------------+------------+
自動(dòng)標(biāo)號(hào)
在插入行的時(shí)候,我額外輸入了一個(gè)ID列,作為標(biāo)號(hào),其實(shí)table提供了相關(guān)的方法和接口,只需要調(diào)用SetAutoIndex方法,增加自動(dòng)的索引列即可:
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 | +---+----------+-----+-------------+------------+------------+
單元格合并
有的時(shí)候,相鄰單元格的值一樣我們可能會(huì)想要進(jìn)行合并,這樣更美觀,單元格合并分為列合并和行合并;先定義一下這里的列合并和行合并:
- 列合并:針對(duì)單列,如果單列中的多個(gè)相鄰行數(shù)據(jù)一樣,那么就合并為一個(gè)大行;
- 行合并:針對(duì)單行,如果單行中的多個(gè)相鄰列數(shù)據(jù)一樣,那么久合并為一個(gè)大列;
這里我們用到的原始表格如下:
+--------------------------------------------------------------+ | 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 | +---+----------+-------+-------------+------------+------------+
列合并
我們先進(jìn)行最后一列AvgRtt的列合并:
func (d *Demo) ColumnMerge() {
d.T.SetColumnConfigs([]table.ColumnConfig{
{
Name: "AvgRtt",
// Number是指定列的序號(hào)
// Number: 5,
AutoMerge: true,
Align: text.AlignCenter,
},
})
}
可以選擇通過(guò)列的表頭或者列的序號(hào)來(lái)選擇具體進(jìn)行合并的列:
+---+----------+-------+-------------+------------+------------+ | | 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 | +---+----------+-------+-------------+------------+------------+
這樣看表格線條不明顯,感覺(jué)不到區(qū)分,那么可以加上一些設(shè)置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 | +---+----------+-------+-------------+------------+------------+
行合并
行合并我們對(duì)最后一行的匯總行進(jìn)行合并,具體做法是在添加匯總行時(shí)增加RowConfig參數(shù):
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 | +---+---------------------------------------------+------------+
樣式設(shè)置
現(xiàn)在整個(gè)表格已經(jīng)生成,但我們還需要進(jìn)行一些美化,這就要對(duì)表格的樣式進(jìn)行設(shè)置了;
居中設(shè)置
對(duì)于居中,無(wú)法直接進(jìn)行全局的設(shè)置,必須根據(jù)列進(jìn)行,如下:
func (d *Demo) SetAlignCenter() {
column := []string{"IP", "Num", "PacketsRecv", "PacketLoss", "AvgRtt"}
c := []table.ColumnConfig{}
// 根據(jù)表格的列數(shù)循環(huán)進(jìn)行設(shè)置,統(tǒng)一居中
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)
}
居中效果如下,這樣既能保留列合并又完成了劇中設(shè)置:
+---+----------+-------+-------------+------------+------------+ | | 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 | +---+---------------------------------------------+------------+
數(shù)字自動(dòng)高亮標(biāo)紅
在我的應(yīng)用場(chǎng)景中,ping的ip如果出現(xiàn)了丟包情況,那就要紅色高亮,方便使用者馬上關(guān)注到,這種情況下,可以通過(guò)Transformer來(lái)設(shè)置:
func (d *Demo) SetWarnColor() {
// 字體顏色
WarnColor := text.Colors{text.BgRed}
warnTransformer := text.Transformer(func(val interface{}) string {
if val.(float64) > 0 {
// 統(tǒng)計(jì)丟包服務(wù)器總數(shù)
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,
},
})
}
實(shí)際效果如下:

完整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是指定列的序號(hào)
// Number: 5,
AutoMerge: true,
Align: text.AlignCenter,
},
})
}
func (d *Demo) SetAlignCenter() {
column := []string{"IP", "Num", "PacketsRecv", "PacketLoss", "AvgRtt"}
c := []table.ColumnConfig{}
// 根據(jù)表格的列數(shù)循環(huán)進(jìn)行設(shè)置,統(tǒng)一居中
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 {
// 統(tǒng)計(jì)丟包服務(wù)器總數(shù)
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()
}
結(jié)語(yǔ)
本文介紹了使用第三方庫(kù)美化Golang的命令行表格格式化輸出,除了table以外,go-pretty庫(kù)中還包含了進(jìn)度條、列表等美化方法,感興趣可以自己看看官方文檔。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
golang Iris運(yùn)行多個(gè)應(yīng)用的實(shí)現(xiàn)
本文主要介紹了golang Iris運(yùn)行多個(gè)應(yīng)用的實(shí)現(xiàn),在Iris里面,提供了一種方式可以讓我們同時(shí)運(yùn)行多個(gè)應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
golang基礎(chǔ)之Gocurrency并發(fā)
這篇文章主要介紹了golang基礎(chǔ)之Gocurrency并發(fā),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
一文帶你熟悉Go語(yǔ)言中的分支結(jié)構(gòu)
這篇文章主要和大家分享一下Go語(yǔ)言中的分支結(jié)構(gòu)(if?-?else-if?-?else、switch),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以參考一下2022-11-11
Go單體服務(wù)開(kāi)發(fā)最佳實(shí)踐總結(jié)
這篇文章主要介紹了Go單體服務(wù)開(kāi)發(fā)最佳實(shí)踐,通過(guò)本文詳細(xì)跟大家分享一下如何使用?go-zero?快速開(kāi)發(fā)一個(gè)有多個(gè)模塊的單體服務(wù),需要的朋友可以參考下2022-04-04

