Как остановить цветовые коды ANSI, которые портят выравнивание printf? - PullRequest
2 голосов
/ 16 сентября 2010

Я обнаружил это при использовании ruby ​​printf, но это также относится и к printf языка C.

Если вы включите escape-коды цвета ANSI в выходную строку, это испортит выравнивание.

Ruby:

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

Одна и та же строка в программе на Си выдает тот же вывод.

Есть ли способ заставить printf (или что-то еще) выравнивать вывод и не добавлять пробелы для непечатных символов?

Это ошибка, или для этого есть веская причина?

Обновление: Поскольку на printf нельзя полагаться для выравнивания данных при наличии кодов ANSI и широких символов, есть ли лучший способ выстраивать цветные табличные данные в консоли в ruby?

Ответы [ 5 ]

5 голосов
/ 16 сентября 2010

Это не ошибка: ruby ​​не должен знать (по крайней мере, внутри printf, это было бы другой историей для чего-то вроде curses), что его стандартный вывод направляется на терминал, который понимает escape-последовательности VT100.

Если вы не настраиваете фоновые цвета, лучше подойдет что-то вроде этого:

GREEN = "\033[32m"
NORMAL = "\033[0m"
printf "%s%20s%s\n", GREEN, "Green", NORMAL
4 голосов
/ 16 сентября 2010

Я не согласен с вашей характеристикой «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 переменные (константы?) содержат escape-последовательности для переключения на названный цвет(и co_plain может быть лучше, чем co_black).Если выясняется, что для какого-то поля вам не нужна колоризация, вы можете использовать пустую строку вместо co_XXXX переменных (или назвать ее co_empty).

3 голосов
/ 16 сентября 2010

Я бы отделил любые escape-последовательности от реального текста, чтобы избежать всего вопроса.

# 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");

Поскольку escape-последовательности ANSI не являются частью Ruby или C, они также не думают, что им нужно относиться к этим символам особым образом, и это справедливо.

Если вы собираетесь выполнять много цветовых операций с терминалами, вам следует изучить проклятия и ncurses, которые предоставляют функции для изменения цвета, которые работают для многих различных типов терминалов. Они также предоставляют гораздо больше функциональных возможностей, таких как текстовые окна, функциональные клавиши и иногда даже взаимодействие с мышью.

3 голосов
/ 16 сентября 2010

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 для ширины поля переменной среды выполнения.

0 голосов
/ 16 марта 2012

Вот решение, которое я недавно придумал. Это позволяет вам использовать color("my string", :red) в операторе printf. Мне нравится использовать одну и ту же строку форматирования для заголовков и данных - СУХОЙ. Это делает это возможным. Кроме того, я использую драгоценный камень rainbow для генерации цветовых кодов; это не идеально, но делает работу. Хэш 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)
]
...