如何阻止ANSI颜色代码搞乱printf对齐?

时间:2010-09-16 03:09:22

标签: terminal language-agnostic console printf ansi-colors

我在使用ruby printf时发现了这一点,但它也适用于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(或其他东西)来对齐输出而不为非打印字符添加空格?

这是一个错误,还是有充分的理由呢?

更新:由于在ANSI代码和宽字符时无法依赖printf来对齐数据,是否有最佳实践方法在ruby中在控制台中排列彩色表格数据?< / p>

5 个答案:

答案 0 :(得分:5)

这不是一个错误:ruby不应该知道(至少在printf中,对于像curses这样的东西会是一个不同的故事),它的stdout会去一个了解VT100转义序列的终端。

如果你没有调整背景颜色,这样的事情可能是个更好的主意:

GREEN = "\033[32m"
NORMAL = "\033[0m"
printf "%s%20s%s\n", GREEN, "Green", NORMAL

答案 1 :(得分:4)

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

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

这向我显示printf()语句在字符串中计算了14个字符,因此它在前面添加了6个空格来生成20个字符的右对齐。然而,终端吞下了其中的9个字符,将它们解释为颜色变化。因此,输出显示比您想要的短9个字符。但是,printf()在第一个“绿色”之后没有打印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)。如果事实证明您在某些字段上不需要着色,则可以使用空字符串代替co_XXXX变量(或称之为co_empty)。

答案 2 :(得分:3)

printf字段宽度说明符对于对齐表格数据,界面元素等没有用。除了您已经发现的控制字符的问题之外,您的程序还会有非间距和双宽度字符。必须处理如果你不想限制遗留字符编码(很多用户认为已弃用)。

如果您坚持以这种方式使用printf,则可能需要执行以下操作:

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函数)来查找每个终端列的数量。请注意在*格式字符串中使用printf代替常量字段宽度。这允许您将int参数传递给printf以获取运行时变量字段宽度。

答案 3 :(得分:3)

我会从实际文本中删除任何转义序列以避免整个问题。

# 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的一部分,因此它们都不认为他们需要特别对待这些字符,并且理所当然。

如果你要做很多终端颜色的东西,那么你应该研究curses和ncurses,它们提供了可以用于许多不同类型终端的颜色变化的功能。它们还提供了更多功能,如基于文本的窗口,功能键,有时甚至是鼠标交互。

答案 4 :(得分:0)

这是我最近提出的解决方案。这允许您在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)
]