使用Go實現(xiàn)在命令行輸出好看的表格
最近在寫一些運維小工具,比如批量進行ping包的工具,實現(xiàn)不困難,反正就是ping,統(tǒng)計,然后輸出,不過我本著自己既是開發(fā)者又是使用者的理念,還是不喜歡輸出特別難看的工具,就像這樣:
所以就去https://pkg.go.dev/瞄了一眼,看看有沒有啥適合的庫能夠把輸出整的好看點的,于是找到了一個庫github.com/jedib0t/go-pretty/v6/table
這是一個在命令行輸出格式化表格的庫,這里記錄一下使用這個庫進行一些格式化輸出的過程。
其實還有一個比較簡單的庫叫做gotable,也能實現(xiàn)基礎的格式化輸出功能,使用起來也方便些,不過功能相對來說就要單一一些,在表格樣式設置上會差一些,沒那么自由
也可以看下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)
看看效果,表頭已經(jīng)打印出來了
+----+----+-----+-------------+------------+--------+ | ID | IP | NUM | PACKETSRECV | PACKETLOSS | AVGRTT | +----+----+-----+-------------+------------+--------+ +----+----+-----+-------------+------------+--------+
插入行
數(shù)據(jù)的插入和表頭的生成類似,要生成一個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 | +----+----------+-----+-------------+------------+-----------+
當然也可以生成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 | +----+----------+-----+-------------+------------+------------+
表格標題
在設置表格實際內(nèi)容時,還可以設置一個表格標題,如下:
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提供了相關的方法和接口,只需要調(diào)用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 | +---+----------+-----+-------------+------------+------------+
單元格合并
有的時候,相鄰單元格的值一樣我們可能會想要進行合并,這樣更美觀,單元格合并分為列合并和行合并;先定義一下這里的列合并和行合并:
- 列合并:針對單列,如果單列中的多個相鄰行數(shù)據(jù)一樣,那么就合并為一個大行;
- 行合并:針對單行,如果單行中的多個相鄰列數(shù)據(jù)一樣,那么久合并為一個大列;
這里我們用到的原始表格如下:
+--------------------------------------------------------------+ | 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 | +---+----------+-------+-------------+------------+------------+
這樣看表格線條不明顯,感覺不到區(qū)分,那么可以加上一些設置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參數(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 | +---+---------------------------------------------+------------+
樣式設置
現(xiàn)在整個表格已經(jīng)生成,但我們還需要進行一些美化,這就要對表格的樣式進行設置了;
居中設置
對于居中,無法直接進行全局的設置,必須根據(jù)列進行,如下:
func (d *Demo) SetAlignCenter() { column := []string{"IP", "Num", "PacketsRecv", "PacketLoss", "AvgRtt"} c := []table.ColumnConfig{} // 根據(jù)表格的列數(shù)循環(huán)進行設置,統(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) }
居中效果如下,這樣既能保留列合并又完成了劇中設置:
+---+----------+-------+-------------+------------+------------+ | | 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ù)字自動高亮標紅
在我的應用場景中,ping的ip如果出現(xiàn)了丟包情況,那就要紅色高亮,方便使用者馬上關注到,這種情況下,可以通過Transformer來設置:
func (d *Demo) SetWarnColor() { // 字體顏色 WarnColor := text.Colors{text.BgRed} warnTransformer := text.Transformer(func(val interface{}) string { if val.(float64) > 0 { // 統(tǒng)計丟包服務器總數(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, }, }) }
實際效果如下:
完整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{} // 根據(jù)表格的列數(shù)循環(huán)進行設置,統(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)計丟包服務器總數(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() }
結語
本文介紹了使用第三方庫美化Golang的命令行表格格式化輸出,除了table以外,go-pretty
庫中還包含了進度條、列表等美化方法,感興趣可以自己看看官方文檔。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。