博客 / 詳情

返回

厭倦了黑底白字?用 Go 給終端點顏色瞧瞧!

如果你每天都在使用終端,想必無法忍受終端永遠都是黑白兩種配色。如果你不知道終端中各種花哨的顏色是如何輸出的,那麼本文就來幫你解答。

而如果你恰巧在使用 Go 語言,那麼你將在一分鐘內學會使用 Go 語言在終端中輸出彩色字符。
image.png

廢話不多説,我們開始吧。

給終端加點顏色

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...")
}

執行示例代碼,得到以下輸出:
image.png

沒錯,就是這麼簡單!

導入第三方包 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.")

執行示例代碼,得到以下輸出:
image.png

我們也可以將內容輸出到任何 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 對象的輸出內容直接寫入文件。

執行示例代碼,得到以下輸出:
image.png

可以使用 vim 打開 output.log 查看文件內容如下:

image.png

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")

執行示例代碼,得到以下輸出:

image.png

最後,我們可以在不修改原來的 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")

執行示例代碼,得到以下輸出:

image.png

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 詳解》。

執行示例代碼,得到以下輸出:

image.png

現在,你已經掌握了使用 fatih/color 在終端輸出彩色字符,如果你對終端輸出彩色字符的原理感興趣,不妨接着往下看。

終端輸出彩色字符原理解析

我們知道,鍵盤中的每一個鍵都在 ASCII 表中。比如字母 A 在 ASCII 表中對應的數字為 65,字母 B 在 ASCII 表中對應的數字為 66。像這些字符,叫「可顯示字符」,輸出到終端是可以被我們看見的。

ASCII 表中還定義了一些不可顯示的字符,它們是 ASCII 表中 0~31 以及 127 這些數字,也叫「控制字符」。

比如經典的「回車」和「換行」字符,CR(Carriage Return) 代表回車,LF(Line Feed)代表換行。因為它們無法顯示,通常在文件傳輸等場景,需要對其進行轉義,所以它們都有對應的轉義字符。CR 的轉義字符是 \rLF 轉義字符是 \nCR 在 ASCII 表中對應的數字為 13LF 在 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 標誌表示開啓轉義,否則只會當作普通文本解析。

image.png

指令生效了。細心的你一定發現了問題,就是接下來的所有輸出都變成了藍色 + 下劃線,如何還原呢?

我們可以使用 \e[0m 指令,它表示重置所有屬性。

image.png

現在,回過頭去看看我們使用 fatih/color 寫的示例代碼,你是不是能大概猜測到它是怎麼實現的。

還記得 output.log 文件內容嗎?
image.png

在 ANSI轉義序列中,控制指令 ESC + [ 一般會被顯示為 ^[[。所以,這個文本內容也就一目瞭然了。

fatih/color 正是使用了 \e[ 控制序列來實現在終端中輸出彩色字符的。

好了,原理部分就講解到這裏,雖然概念比較多,但其實就是一個簡單的指令罷了。

總結

現在你不僅學會使用 Go 語言在終端中輸出帶有顏色的字符,還明白了其實現原理。

無需我多做什麼總結了,如果你有興趣瞭解終端都支持輸出哪些顏色,可以去看看 ANSI轉義序列。

本文示例源碼我都放在了 GitHub 中,歡迎點擊查看。

希望此文能對你有所啓發。

聯繫我

  • 公眾號:Go編程世界
  • 微信:jianghushinian
  • 郵箱:jianghushinian007@outlook.com
  • 博客:https://jianghushinian.cn
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.