代码之家  ›  专栏  ›  技术社区  ›  Stewart Johnson

如何阻止ANSI色码弄乱打印的对齐?

  •  2
  • Stewart Johnson  · 技术社区  · 14 年前

    我在使用Rubyprintf时发现了这一点,但它也适用于C的printf。

    如果在输出字符串中包含ANSI颜色转义码,则会扰乱对齐。

    红宝石:

    ruby-1.9.2-head > printf "%20s\n%20s\n", "\033[32mGreen\033[0m", "Green"
          Green          # 6 spaces to the left of this one
                   Green # correctly padded to 20 chars
     => nil
    

    C程序中的同一行产生相同的输出。

    是否仍要获取printf(或其他内容)以对齐输出,而不为非打印字符添加空格?

    这是一个bug,还是有一个很好的理由?

    更新: 既然在有ANSI代码和宽字符的情况下不能依靠printf来对齐数据,那么在Ruby的控制台中有没有一种最佳的方法来排列彩色表格数据呢?

    5 回复  |  直到 10 年前
        1
  •  4
  •   Jonathan Leffler    14 年前

    我不同意你对“绿后9格”的描述。我使用的是Perl而不是Ruby,但是如果我对您的语句进行修改,在字符串后面打印一个管道符号,我会得到:

    perl -e 'printf "%20s|\n%20s|\n", "\033[32mGreen\033[0m", "Green";'
          Green|
                   Green|
    

    这向我表明 printf() 语句在字符串中计算了14个字符,因此它在前面加了6个空格,以产生20个右对齐的字符。然而,终端吞下了其中的9个字符,将它们解释为颜色变化。因此,输出看起来比您想要的短9个字符。但是, 打印f() 没有在第一个“绿色”后打印9个空白。


    关于对齐输出(带着色)的最佳实践,我认为您需要将每个大小和对齐的字段用处理着色的简单的“%s”字段包围:

    printf "%s%20.20s%s|%s%-10d%s|%s%12.12s%s|\n",
           co_green, column_1_data, co_plain,
           co_blue,  column_2_data, co_plain,
           co_red,   column_3_data, co_plain;
    

    显然,在哪里 co_XXXX 变量(常量?)包含转义序列以切换到指定颜色(和 co_plain 可能会更好 co_black )。如果结果显示在某些字段上不需要着色,则可以使用空字符串代替 公司XXXX 变量(或称之为 co_empty )

        2
  •  5
  •   Jack Kelly    14 年前

    这不是一个bug:Ruby不可能知道(至少在printf中,对于像诅咒这样的东西来说,这是另一回事),它的stdout将进入一个能够理解VT100转义序列的终端。

    如果你不调整背景颜色,这样做可能是个更好的主意:

    GREEN = "\033[32m"
    NORMAL = "\033[0m"
    printf "%s%20s%s\n", GREEN, "Green", NORMAL
    
        3
  •  3
  •   R.. GitHub STOP HELPING ICE    14 年前

    printf 字段宽度说明符不适用于对齐表格数据、接口元素等。除了您已经发现的控制字符问题之外,还有非空格和双宽度字符,如果您不想限制这些字符,您的程序将不得不处理它们。o传统字符编码(许多用户认为已弃用)。

    如果你坚持使用 普林特 这样,您可能需要执行以下操作:

    printf("%*s\n%*s\n", bytestopad("\033[32mGreen\033[0m", 20), "\033[32mGreen\033[0m", bytestopad("Green", 20), "Green");

    在哪里? bytestopad(s,n) 是一个计算产生字符串所需的总字节数(字符串加填充空格)的函数。 s 接受 n 接线柱。这将涉及解析转义和处理多字节字符,以及使用一个工具(如posix wcwidth 函数)查找每个接收的终端列数。注意使用 * 普林特 格式化字符串。这允许你通过 int 参数 普林特 对于运行时变量字段宽度。

        4
  •  3
  •   nategoose    14 年前

    我会从实际文本中分离出任何转义序列,以避免整个问题。

    # in Ruby
    printf "%s%20s\n%s%20s\n", "\033[32m", "Green", "\033[0m", "Green"
    

    /* In C */
    printf("%s%20s\n%s%20s\n", "\033[32m", "Green", "\033[0m", "Green");
    

    由于ansi转义序列不是ruby或c的一部分,所以它们都不认为需要对这些字符进行特殊的处理,这是正确的。

    如果你要做很多终端颜色的事情,那么你应该看看诅咒和奴隶制,它们提供了对许多不同类型终端进行颜色更改的功能。它们还提供了更多的功能,如基于文本的窗口、功能键,有时甚至鼠标交互。

        5
  •  0
  •   Andrew Ashbacher    13 年前

    这是我最近提出的一个解决方案。这允许你使用 color("my string", :red) 在一个 printf 声明。我喜欢对标题和数据使用相同的格式化字符串——dry。这使得这成为可能。另外,我使用 rainbow gem来生成颜色代码;它并不完美,但可以完成任务。这个 CPAD 哈希包含每种颜色的两个值,分别对应于左填充和右填充。当然,这个解决方案应该扩展,以方便其他颜色和修改器,如粗体和下划线。

    CPAD = {
      :default => [0, 2],
      :green   => [0, 3],
      :yellow  => [0, 2],
      :red     => [0, 1],
    }
    
    def color(text, color)
      "%*s%s%*s" % [CPAD[color][0], '', text.color(color), CPAD[color][1], '']
    end
    

    例子:

    puts "%-10s   %-10s   %-10s   %-10s" % [
      color('apple',  :red),
      color('pear',   :green),
      color('banana', :yellow)
      color('kiwi',   :default)
    ]
    
    推荐文章