编程 go-colorable:让 Go CLI 在 Windows 终端正确显示 ANSI 颜色

2026-09-12 09:48:49

go-colorable:让 Go CLI 在 Windows 终端正确显示 ANSI 颜色

项目地址:https://github.com/mattn/go-colorable

1. 为什么需要 go-colorable

先看一个场景:给 CLI 工具加彩色输出,直接用 ANSI 转义码:

package main

import "fmt"

func main() {
    // 红色文字
    fmt.Println("\033[31m这是红色文字\033[0m")

    // 绿色文字
    fmt.Println("\033[32m这是绿色文字\033[0m")

    // 蓝色文字
    fmt.Println("\033[34m这是蓝色文字\033[0m")
}

在 Linux/macOS 上显示彩色,在 Windows 上会显示 \033[31m这是红色文字\033[0m 这样的原始字符。因为 Windows 的 cmd.exe 默认不支持 ANSI 转义码。

用 go-colorable 包装 stdout:

package main

import (
    "fmt"

    "github.com/mattn/go-colorable"
)

func main() {
    // 使用 colorable 包装 stdout
    stdout := colorable.NewColorableStdout()

    // Windows 也能正确显示颜色
    fmt.Fprintln(stdout, "\033[31m这是红色文字\033[0m")
    fmt.Fprintln(stdout, "\033[32m这是绿色文字\033[0m")
    fmt.Fprintln(stdout, "\033[34m这是蓝色文字\033[0m")
}

跨平台兼容问题解决。

2. go-colorable 是什么

go-colorable 是一个 Go 语言的跨平台彩色终端库。官方描述:

colorable is a wrapper for Windows console that enables ANSI escape sequences.

即 Windows 控制台包装器,启用 ANSI 转义序列,提供跨平台兼容。

核心定位:不是彩色库,也不是 TUI 框架,而是一个 ANSI 兼容层,专门解决 Windows 的 ANSI 支持问题。它做的事:

  1. 检测是否为 Windows
  2. 如果是,启用 ANSI 支持
  3. 包装 stdout/stderr
  4. 透明处理颜色输出

3. 核心特性:跨平台和透明

跨平台:

// 自动检测平台
// Windows:启用 ANSI 支持
// Linux/macOS:直接透传
stdout := colorable.NewColorableStdout()
stderr := colorable.NewColorableStderr()

透明处理:

// 像使用普通 stdout 一样使用
fmt.Fprintln(stdout, "普通文字")
fmt.Fprintln(stdout, "\033[31m红色文字\033[0m")
fmt.Fprintln(stdout, "\033[1;32m粗体绿色\033[0m")

颜色代码:

// 前景色
fmt.Fprintln(stdout, "\033[30m黑色\033[0m")
fmt.Fprintln(stdout, "\033[31m红色\033[0m")
fmt.Fprintln(stdout, "\033[32m绿色\033[0m")
fmt.Fprintln(stdout, "\033[33m黄色\033[0m")
fmt.Fprintln(stdout, "\033[34m蓝色\033[0m")
fmt.Fprintln(stdout, "\033[35m紫色\033[0m")
fmt.Fprintln(stdout, "\033[36m青色\033[0m")
fmt.Fprintln(stdout, "\033[37m白色\033[0m")

// 背景色
fmt.Fprintln(stdout, "\033[41m红色背景\033[0m")
fmt.Fprintln(stdout, "\033[42m绿色背景\033[0m")

// 样式
fmt.Fprintln(stdout, "\033[1m粗体\033[0m")
fmt.Fprintln(stdout, "\033[4m下划线\033[0m")

4. 安装和使用

安装:

go get github.com/mattn/go-colorable

包装 stdout/stderr

package main

import (
    "fmt"

    "github.com/mattn/go-colorable"
)

func main() {
    stdout := colorable.NewColorableStdout()
    stderr := colorable.NewColorableStderr()

    fmt.Fprintln(stdout, "\033[32m成功信息\033[0m")
    fmt.Fprintln(stderr, "\033[31m错误信息\033[0m")
}

配合 log 包使用

package main

import (
    "log"

    "github.com/mattn/go-colorable"
)

func main() {
    // 设置日志输出到 colorable
    log.SetOutput(colorable.NewColorableStdout())

    log.Println("\033[32m[INFO]\033[0m 这是信息日志")
    log.Println("\033[33m[WARN]\033[0m 这是警告日志")
    log.Println("\033[31m[ERROR]\033[0m 这是错误日志")
}

自定义 Writer

package main

import (
    "fmt"
    "os"

    "github.com/mattn/go-colorable"
)

func main() {
    // 包装任意 writer
    writer := colorable.NewColorable(os.Stdout)

    fmt.Fprintln(writer, "\033[36m青色文字\033[0m")
    fmt.Fprintln(writer, "\033[35m紫色文字\033[0m")
}

检测是否支持颜色

package main

import (
    "fmt"
    "os"

    "github.com/mattn/go-isatty"
)

func main() {
    // 检测 stdout 是否为终端
    if isatty.IsTerminal(os.Stdout.Fd()) {
        fmt.Println("\033[32m终端支持颜色\033[0m")
    } else {
        fmt.Println("非终端,不输出颜色")
    }
}

5. 核心功能

go-colorable 定位是 ANSI 兼容层,配合其他库可以完成更完整的功能。

配合 go-color

package main

import (
    "github.com/fatih/color"
)

func main() {
    // go-color 内部使用 go-colorable
    color.Red("这是红色")
    color.Green("这是绿色")
    color.Blue("这是蓝色")

    // 自定义样式
    customStyle := color.New(color.FgCyan, color.Bold)
    customStyle.Println("这是粗体青色")
}

256 色支持

func main() {
    stdout := colorable.NewColorableStdout()

    // 256 色
    for i := 0; i < 256; i++ {
        fmt.Fprintf(stdout, "\033[38;5;%dm%d ", i, i)
        if (i+1)%16 == 0 {
            fmt.Fprintln(stdout, "\033[0m")
        }
    }
}

RGB 真彩色

func main() {
    stdout := colorable.NewColorableStdout()

    // RGB 真彩色
    fmt.Fprintln(stdout, "\033[38;2;255;0;0m红色\033[0m")
    fmt.Fprintln(stdout, "\033[38;2;0;255;0m绿色\033[0m")
    fmt.Fprintln(stdout, "\033[38;2;0;0;255m蓝色\033[0m")
}

光标控制

func main() {
    stdout := colorable.NewColorableStdout()

    // 移动光标
    fmt.Fprint(stdout, "\033[2J")      // 清屏
    fmt.Fprint(stdout, "\033[H")       // 移动到家
    fmt.Fprintln(stdout, "第一行")
    fmt.Fprint(stdout, "\033[2;1H")    // 移动到第2行第1列
    fmt.Fprintln(stdout, "第二行")
}

6. 实战场景

场景一:彩色日志系统

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/mattn/go-colorable"
)

type Logger struct {
    infoLog  *log.Logger
    warnLog  *log.Logger
    errorLog *log.Logger
}

func NewLogger() *Logger {
    stdout := colorable.NewColorableStdout()
    stderr := colorable.NewColorableStderr()

    return &Logger{
        infoLog:  log.New(stdout, "", 0),
        warnLog:  log.New(stdout, "", 0),
        errorLog: log.New(stderr, "", 0),
    }
}

func (l *Logger) Info(format string, args ...interface{}) {
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    msg := fmt.Sprintf(format, args...)
    l.infoLog.Printf("\033[32m[INFO]\033[0m %s %s", timestamp, msg)
}

func (l *Logger) Warn(format string, args ...interface{}) {
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    msg := fmt.Sprintf(format, args...)
    l.warnLog.Printf("\033[33m[WARN]\033[0m %s %s", timestamp, msg)
}

func (l *Logger) Error(format string, args ...interface{}) {
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    msg := fmt.Sprintf(format, args...)
    l.errorLog.Printf("\033[31m[ERROR]\033[0m %s %s", timestamp, msg)
}

func main() {
    logger := NewLogger()

    logger.Info("服务器启动成功")
    logger.Warn("内存使用率超过 80%%")
    logger.Error("数据库连接失败")
}

效果:INFO 绿色,WARN 黄色,ERROR 红色。

场景二:测试结果展示

package main

import (
    "fmt"

    "github.com/mattn/go-colorable"
)

type TestResult struct {
    Name   string
    Passed bool
}

func displayResults(results []TestResult) {
    stdout := colorable.NewColorableStdout()

    passed := 0
    failed := 0

    for _, r := range results {
        if r.Passed {
            fmt.Fprintf(stdout, "\033[32m✓\033[0m %s\n", r.Name)
            passed++
        } else {
            fmt.Fprintf(stdout, "\033[31m✗\033[0m %s\n", r.Name)
            failed++
        }
    }

    fmt.Fprintln(stdout)
    fmt.Fprintf(stdout, "总计:%d 通过,%d 失败\n", passed, failed)

    if failed == 0 {
        fmt.Fprintf(stdout, "\033[32m所有测试通过!\033[0m\n")
    } else {
        fmt.Fprintf(stdout, "\033[31m有测试失败\033[0m\n")
    }
}

func main() {
    results := []TestResult{
        {"测试用户登录", true},
        {"测试数据验证", true},
        {"测试权限检查", false},
        {"测试数据持久化", true},
    }

    displayResults(results)
}

效果:通过绿色 ✓,失败红色 ✗。

场景三:进度显示

package main

import (
    "fmt"
    "time"

    "github.com/mattn/go-colorable"
)

func showProgress(total int) {
    stdout := colorable.NewColorableStdout()

    for i := 0; i <= total; i++ {
        percent := float64(i) / float64(total) * 100

        // 根据进度显示不同颜色
        var color string
        if percent < 30 {
            color = "\033[31m" // 红色
        } else if percent < 70 {
            color = "\033[33m" // 黄色
        } else {
            color = "\033[32m" // 绿色
        }

        fmt.Fprintf(stdout, "\r%s%.1f%%\033[0m", color, percent)
        time.Sleep(50 * time.Millisecond)
    }

    fmt.Fprintln(stdout)
}

func main() {
    fmt.Println("下载进度:")
    showProgress(100)
    fmt.Println("\033[32m下载完成!\033[0m")
}

效果:进度从红到黄到绿。

7. 设计亮点

  • 跨平台兼容:自动处理 Windows ANSI 支持,一套代码到处运行,无需平台判断,透明处理。
  • 零配置:开箱即用,无需配置,集成快,减少错误。
  • 轻量级:代码量小,无依赖,编译快,体积小,易于维护。
  • 标准兼容:使用标准 ANSI 转义码,学习成本低,文档丰富,工具支持广。
  • 生态完善:可配合 go-color、go-isatty 等库使用,功能丰富,灵活扩展,社区支持。

8. 和类似方案对比

vs. go-color

维度go-colorablego-color
定位ANSI 兼容彩色 API
使用底层包装高层 API
关系基础设施基于 colorable

go-colorable 是基础,go-color 更高级。

vs. 直接 ANSI

维度go-colorable直接 ANSI
跨平台支持Windows 不支持
兼容性
易用性

在跨平台场景下 go-colorable 更合适。

vs. 其他方案

维度go-colorableansicolor
Star1.3K500
维护活跃较少
生态完善有限

在维护活跃度和生态上 go-colorable 更占优。

9. 局限性

  • 功能单一:只做 ANSI 兼容
  • 需要配合:需要配合其他库使用
  • Windows 限制:某些旧 Windows 不支持
  • 终端限制:某些终端不支持 256 色
  • 性能开销:包装有一定开销
  • 调试困难:颜色代码调试较难

10. 总结

go-colorable 解决的问题很具体:Windows 终端的 ANSI 兼容。它的设计思路:

  1. 跨平台很重要:一套代码到处运行
  2. 兼容性是基础:解决平台差异
  3. 简单设计有价值:专注解决一个问题
  4. 生态完善很重要:配合其他库使用
  5. 小工具解决大问题:看似简单,实则关键

如果正在处理 Windows 的 ANSI 支持问题,go-colorable 是一个可用的方案。它让 CLI 工具在不同平台上输出一致的颜色。


参考资料

复制全文 生成海报 Go CLI go-colorable ANSI 终端

推荐文章

程序员茄子在线接单