Golang中如何获取Unicode字符串的"可见"长度

Golang中如何获取Unicode字符串的"可见"长度 有没有一种简单的方法可以获取字符串的可见长度?我的程序返回以下字符串:

e[0;97;44;1m per e[0;34;104me[0me[0me[0;97;104;1m 2021-11-15 13:06 e[0;94;107me[0me[0me[0;94;107;1m ~/code/goprompt e[0;97;104me[0me[0me[0;97;104;1m go 1.17 e[0;94;44me[0me[0me[0;97;44;1m master|~3 e[0;34me[0me[0m 

这个字符串超过200字节,但其中大部分字符是不可打印的,比如颜色代码等。终端中的实际输出是这样的:

image

我感兴趣的是这个提示符在终端中的长度,如果我数得没错的话,大约是67个字符。我尝试了几种不同的方法:

	fmt.Println(len(result))                      // 返回 229
	fmt.Println(len([]rune(result)))              // 返回 219
	fmt.Println(utf8.RuneCountInString(result))   // 返回 219

我基本上想要一个 utf8.**Visible**RuneCountInString(result) 这样的函数。有人知道这样的函数吗?


更多关于Golang中如何获取Unicode字符串的"可见"长度的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

啊,我明白你的意思了。那我就手动解决它,通过在应用着色之前计算长度。听起来可行……谢谢!

更多关于Golang中如何获取Unicode字符串的"可见"长度的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这些终端转义序列是 Unix shell 特有的(而且很可能只针对特定的 shell,如 bash 或 zsh)。它们存在于 Unicode 时代之前,因此像 RuneCountInString 这样的 Unicode 函数在这里并不适用。

你需要一个自定义的长度函数,该函数能够识别这些 shell 转义序列以及它们在终端中的视觉表示大小。不确定是否存在这样的现成函数。

或许确实存在一些可用的长度函数。在 pkg.go.dev 上搜索 escape sequence terminal 会得到一些结果,其中包括

text package - github.com/MichaelMure/go-term-text - pkg.go.dev

该包有一个 Len() 函数,在计算字符数时会忽略转义序列。

在Golang中,处理包含ANSI转义码的字符串时,确实需要特殊处理来获取可见字符长度。你可以使用正则表达式来过滤掉这些控制序列:

package main

import (
    "fmt"
    "regexp"
    "unicode/utf8"
)

func visibleRuneCount(s string) int {
    // 匹配ANSI转义序列的正则表达式
    ansiEscapeRegex := regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
    
    // 移除所有ANSI转义序列
    cleanString := ansiEscapeRegex.ReplaceAllString(s, "")
    
    // 计算清理后字符串的rune数量
    return utf8.RuneCountInString(cleanString)
}

func main() {
    str := `e[0;97;44;1m per e[0;34;104me[0me[0me[0;97;104;1m 2021-11-15 13:06 e[0;94;107me[0me[0me[0;94;107;1m ~/code/goprompt e[0;97;104me[0me[0me[0;97;104;1m go 1.17 e[0;94;44me[0me[0me[0;97;44;1m master|~3 e[0;34me[0me[0m`
    
    // 修复字符串中的转义字符(实际应该是\x1b而不是"e")
    fixedStr := ""
    for i := 0; i < len(str); i++ {
        if str[i] == 'e' && i+1 < len(str) && str[i+1] == '[' {
            fixedStr += "\x1b["
            i++ // 跳过'['
        } else {
            fixedStr += string(str[i])
        }
    }
    
    visibleLen := visibleRuneCount(fixedStr)
    fmt.Printf("可见字符长度: %d\n", visibleLen)  // 应该返回约67
}

如果你需要更精确的处理,特别是对于包含Unicode组合字符的字符串,可以使用golang.org/x/text/width包:

package main

import (
    "fmt"
    "regexp"
    "unicode/utf8"
    "golang.org/x/text/width"
)

func visibleDisplayWidth(s string) int {
    // 移除ANSI转义序列
    ansiEscapeRegex := regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
    cleanString := ansiEscapeRegex.ReplaceAllString(s, "")
    
    // 计算显示宽度(考虑全角/半角字符)
    displayWidth := 0
    for _, r := range cleanString {
        switch width.LookupRune(r).Kind() {
        case width.EastAsianWide, width.EastAsianFullwidth:
            displayWidth += 2
        default:
            displayWidth += 1
        }
    }
    return displayWidth
}

func main() {
    str := `e[0;97;44;1m per e[0;34;104me[0me[0me[0;97;104;1m 2021-11-15 13:06 e[0;94;107me[0me[0me[0;94;107;1m ~/code/goprompt e[0;97;104me[0me[0me[0;97;104;1m go 1.17 e[0;94;44me[0me[0me[0;97;44;1m master|~3 e[0;34me[0me[0m`
    
    // 修复转义字符
    fixedStr := ""
    for i := 0; i < len(str); i++ {
        if str[i] == 'e' && i+1 < len(str) && str[i+1] == '[' {
            fixedStr += "\x1b["
            i++
        } else {
            fixedStr += string(str[i])
        }
    }
    
    visibleCount := utf8.RuneCountInString(ansiEscapeRegex.ReplaceAllString(fixedStr, ""))
    displayWidth := visibleDisplayWidth(fixedStr)
    
    fmt.Printf("可见rune数量: %d\n", visibleCount)
    fmt.Printf("终端显示宽度: %d\n", displayWidth)
}

第一个示例提供了基本的可见字符计数,第二个示例考虑了字符在终端中的实际显示宽度(全角字符占2个位置)。根据你的字符串内容,visibleRuneCount函数应该返回大约67个可见字符。

回到顶部