如果你每天都在使用終端,想必無法忍受終端永遠都是黑白兩種配色。如果你不知道終端中各種花哨的顏色是如何輸出的,那麼本文就來幫你解答。
而如果你恰巧在使用 Go 語言,那麼你將在一分鐘內學會使用 Go 語言在終端中輸出彩色字符。
廢話不多説,我們開始吧。
給終端加點顏色
Go 社區中有一個叫 fatih/color 的包,可以非常方便的為輸出的字符添加顏色。並且這個庫用法非常簡單,我來帶你極速上手。
示例代碼如下:
package main
import (
"github.com/fatih/color"
)
func main() {
color.Cyan("Prints text in cyan.")
color.Blue("Prints %s in blue.", "text")
color.Red("Prints text in red.")
color.Magenta("And many others...")
}
執行示例代碼,得到以下輸出:
沒錯,就是這麼簡單!
導入第三方包 fatih/color,使用 color.Blue 輸出藍色,然後使用 color.Red 輸出紅色。
現在你已經學會了使用 Go 語言在終端中輸出彩色字符 :)。
當然,fatih/color 還有更多玩法。它不僅能改變輸出字符顏色,還能同時給字符設置一些其他屬性,比如字體加粗、下劃線等。
示例代碼如下:
// 創建一個 color 對象,輸出效果:藍綠色 + 下劃線
c := color.New(color.FgCyan).Add(color.Underline)
c.Println("Prints cyan text with an underline.") // 注意 Println 輸出自動加換行
// 輸出效果:藍綠色 + 加粗
d := color.New(color.FgCyan, color.Bold)
d.Printf("This prints bold cyan %s\n", "too!.") // 注意 Printf 需要手動加換行
// 輸出效果:紅色 + 白色背景
red := color.New(color.FgRed)
whiteBackground := red.Add(color.BgWhite)
whiteBackground.Println("Red text with white background.")
執行示例代碼,得到以下輸出:
我們也可以將內容輸出到任何 io.Writer 對象:
f, _ := os.OpenFile("output.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
color.New(color.FgBlue).Fprintln(f, "blue color!")
blue := color.New(color.FgBlue)
blue.Fprint(f, "This will print text in blue.\n")
這裏將 color 對象的輸出內容直接寫入文件。
執行示例代碼,得到以下輸出:
可以使用 vim 打開 output.log 查看文件內容如下:
NOTE:
至於文件內容為什麼長這樣,稍後分析。
fatih/color 還支持混合輸出普通字符和帶顏色的字符:
// 輸出效果:普通文本 + 黃色 warning + 紅色 error
yellow := color.New(color.FgYellow).SprintFunc()
red := color.New(color.FgRed).SprintFunc()
fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
// 模擬輸出日誌內容
fmt.Println(color.GreenString("Info:"), "a info log message")
fmt.Printf("%v: %v\n", color.RedString("Warn"), "a warning log message")
執行示例代碼,得到以下輸出:
最後,我們可以在不修改原來的 fmt.PrintX 代碼的情況下,改變其輸出內容屬性:
// 修改標準輸出
color.Set(color.FgYellow)
fmt.Println("Existing text will now be in yellow")
fmt.Printf("This one %s\n", "too")
color.Unset() // 恢復設置
fmt.Println("This is normal text")
func() {
// 在函數中使用
color.Set(color.FgMagenta, color.Bold)
defer color.Unset()
fmt.Println("All text will now be bold magenta.")
}()
fmt.Println("This is normal text too")
執行示例代碼,得到以下輸出:
fatih/color 更多用法,可以參考官方 README.md。
現在我們對 fatih/color 進行一個小實戰,用 Cobra 寫一個命令行 demo 程序 lscolor。它模仿 Linux 的 ls 命令,對目錄或文件輸出不同顏色。
代碼如下:
package main
import (
"fmt"
"os"
"github.com/fatih/color"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "lscolor",
Short: "lscolor lists files and directories with colors",
Args: cobra.MaximumNArgs(1), // 允許零個或一個參數
Run: run,
}
func run(cmd *cobra.Command, args []string) {
dir := "." // 默認使用當前目錄
if len(args) > 0 {
dir = args[0] // 如果提供了參數,則使用該參數
}
// 獲取當前目錄的文件和子目錄
entries, err := os.ReadDir(dir)
if err != nil {
color.Red("error reading directory: %s", err)
return
}
// 遍歷並輸出每個條目
for _, entry := range entries {
if entry.IsDir() {
color.Cyan(entry.Name()) // 目錄用藍綠色
} else {
color.White(entry.Name()) // 文件用白色
}
}
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
NOTE:
如果你對 Cobra 不熟悉,可以參考我的另一篇文章:《Go 語言現代命令行框架 Cobra 詳解》。
執行示例代碼,得到以下輸出:
現在,你已經掌握了使用 fatih/color 在終端輸出彩色字符,如果你對終端輸出彩色字符的原理感興趣,不妨接着往下看。
終端輸出彩色字符原理解析
我們知道,鍵盤中的每一個鍵都在 ASCII 表中。比如字母 A 在 ASCII 表中對應的數字為 65,字母 B 在 ASCII 表中對應的數字為 66。像這些字符,叫「可顯示字符」,輸出到終端是可以被我們看見的。
ASCII 表中還定義了一些不可顯示的字符,它們是 ASCII 表中 0~31 以及 127 這些數字,也叫「控制字符」。
比如經典的「回車」和「換行」字符,CR(Carriage Return) 代表回車,LF(Line Feed)代表換行。因為它們無法顯示,通常在文件傳輸等場景,需要對其進行轉義,所以它們都有對應的轉義字符。CR 的轉義字符是 \r,LF 轉義字符是 \n。CR 在 ASCII 表中對應的數字為 13,LF 在 ASCII 表中對應的數字為 10。
控制字符很好用,可以控制終端的各種屬性。但現有的控制字符還是太少了,有些需求無法滿足,比如給終端輸出的文字加上顏色。因此就有了可以使用轉義序列來控制終端屬性的方法。
以轉義字符開頭的字符序列被叫做轉義序列,所以轉義序列通常由轉義字符後跟普通字符序列組成。轉義字符 + 幾個普通字符組成的轉義序列就能作為一個控制字符。
終端在收到轉義符時,會把其後面的緊挨着幾個字符當作指令來解析,這樣就能實現相應的控制。此外,我們還需要一個表示轉義序列結束的標誌,轉義序列結束後,可以緊挨着普通的文本字符。終端在識別出有效的轉義序列結束後,會執行控制命令,隨後打印所接收到的普通字符。
鋪墊了這麼多,是時候來講解終端是如何輸出彩色字符的了。
一個控制終端輸出顏色的轉義序列長這樣:\e[34;4m。
其中,\e 很顯然是一個轉義字符,表示 ESC 鍵。ESC 在 ASCII 表中對應的數字為 27,十六進制是 0x1B,八進制是 033,所以 \e、\0x1B、\033 寫法都是等價的。
ESC + [ 表示控制序列導入器(Control Sequence Introducer),簡稱 CSI,這是由 ANSI轉義序列 規定的。所以 \e[ 就表示這是一個控制序列,告訴終端控制序列開始了,接下來幾個字符不要當作普通字符輸出,要作為指令來解析。
34;4 都是普通字符,但是在這裏,它們是指令。; 是一個分隔符,用來分隔多個指令,34 代表紅色,4 代表下劃線。這個指令的完整格式是 [<PREFIX>];[<COLOR>];[<TEXT DECORATION>],即 前綴;顏色;文本裝飾,每個指令之間使用 ; 分隔,每個指令都可以省略,顯然這裏省略了 PREFIX 指令。
最後的字符 m 想必你已經猜到了,沒錯,它就是控制序列結束的標誌。
所以 \e[34;4m 就表示:輸出藍色並且帶有下劃線。
我們可以執行 echo -e '\033[34;4mHello' 命令嘗試一下,看看結果:
NOTE:
echo的-e標誌表示開啓轉義,否則只會當作普通文本解析。
指令生效了。細心的你一定發現了問題,就是接下來的所有輸出都變成了藍色 + 下劃線,如何還原呢?
我們可以使用 \e[0m 指令,它表示重置所有屬性。
現在,回過頭去看看我們使用 fatih/color 寫的示例代碼,你是不是能大概猜測到它是怎麼實現的。
還記得 output.log 文件內容嗎?
在 ANSI轉義序列中,控制指令 ESC + [ 一般會被顯示為 ^[[。所以,這個文本內容也就一目瞭然了。
fatih/color 正是使用了 \e[ 控制序列來實現在終端中輸出彩色字符的。
好了,原理部分就講解到這裏,雖然概念比較多,但其實就是一個簡單的指令罷了。
總結
現在你不僅學會使用 Go 語言在終端中輸出帶有顏色的字符,還明白了其實現原理。
無需我多做什麼總結了,如果你有興趣瞭解終端都支持輸出哪些顏色,可以去看看 ANSI轉義序列。
本文示例源碼我都放在了 GitHub 中,歡迎點擊查看。
希望此文能對你有所啓發。
聯繫我
- 公眾號:Go編程世界
- 微信:jianghushinian
- 郵箱:jianghushinian007@outlook.com
- 博客:https://jianghushinian.cn