Terminal 如何阻止ANSI色码弄乱printf对齐?

Terminal 如何阻止ANSI色码弄乱printf对齐?,terminal,language-agnostic,console,printf,ansi-colors,Terminal,Language Agnostic,Console,Printf,Ansi Colors,我在使用ruby printf时发现了这一点,但它也适用于C的printf 如果在输出字符串中包含ANSI颜色转义码,则会打乱对齐 红宝石: C程序中的同一行产生相同的输出 是否需要让printf(或其他东西)对齐输出,而不为非打印字符添加空格 这是一个bug,还是有充分的理由 更新:由于在有ANSI代码和宽字符的情况下不能依靠printf来对齐数据,是否有一种在ruby控制台中排列彩色表格数据的最佳实践方法?这不是一个bug:ruby不可能知道(至少在printf中,对于诅咒之类的东西来说,情

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

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

红宝石:

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

是否需要让printf(或其他东西)对齐输出,而不为非打印字符添加空格

这是一个bug,还是有充分的理由


更新:由于在有ANSI代码和宽字符的情况下不能依靠printf来对齐数据,是否有一种在ruby控制台中排列彩色表格数据的最佳实践方法?

这不是一个bug:ruby不可能知道(至少在printf中,对于诅咒之类的东西来说,情况会有所不同)它的stdout将发送到一个理解VT100逃逸序列的终端

如果您不调整背景颜色,这样做可能会更好:

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

printf
字段宽度说明符对于对齐表格数据、接口元素等没有用处。除了您已经发现的控制字符问题之外,如果你不想把事情限制到遗留字符编码(很多用户认为不推荐),你的程序还需要处理非空格和双宽字符。 如果您坚持以这种方式使用
printf
,您可能需要执行以下操作:

printf(“%*s\n%*s\n”,bytestopad(“\033[32mGreen\033[0m],20),”\033[32mGreen\033[0m”,bytestopad(“绿色”,20),“绿色”);


其中,
bytestopad(s,n)
是您编写的一个函数,用于计算字符串
s
占用
n
终端列所需的总字节数(字符串加上填充空格)。这将涉及解析转义和处理多字节字符,并使用一个工具(如POSIX
wcwidth
函数)查找每个终端列的数量。请注意在
printf
格式字符串中使用
*
代替恒定的字段宽度。这允许您将
int
参数传递给
printf
以获得运行时可变字段宽度。

我不同意您对“绿色后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
)。

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

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

由于ANSI转义序列不是Ruby或C的一部分,所以他们都不认为需要对这些字符进行特殊处理,这是理所当然的


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

这里有一个解决方案n我最近想到的。这允许您使用
颜色(“我的字符串”,:红色)
printf
语句中。我喜欢对标题和数据使用相同的格式字符串--DRY。这使得这成为可能。此外,我使用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)
]

我不确定我是否遵循了你的答案——终端理解转义序列,因为文本显示为绿色。它只是神秘地添加了9个空格(我不明白——转义序列中只有7个字符)@Stewart,终端可能理解它们,但不是终端进行格式化。
printf
唯一知道的是字符,而不是转义序列。你在两个不同的抽象层次上工作。杰克,我甚至会做
#define green“\033[32m”
(或者其他ruby等价物)并直接在
printf
中使用
green
printf“%s%20s%s\n”,绿色,“green”,正常的
。这将使代码更具可读性。我明白你现在所说的。我不明白printf是如何将7个字符的转义序列转换成9个空格的。@Stewart Johnson:
printf
只看到一个14个字符的序列,你想在一个20个字符的字段中打印,所以它会输出6个空格,然后是字符串。它只是没有意识到转义序列将是“不可见的”。它也不在乎转义序列在字符串中的什么位置-开始、中间或结束,效果将是相同的。谢谢-那么在ruby控制台中排列表格数据的最佳实践是什么呢?FWIW,您也可以使用p中的
*
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)
]