Некоторые сведения о Perl 5/Форматированный вывод

Материал из Викиучебника — открытых книг для открытого мира
← Ввод и вывод Глава Ссылки →
Форматированный вывод


Язык Perl изначально задумывался как средство генерации отчётов и вывода их на печать либо на экран, либо на принтер. Для формирования отчётов в язык были включены форматы — специальные строки, которые определяют ширину полей и некоторые правила размещения в них данных (например выравнивание в поле).

В этой главе мы разберём, как может быть реализован форматированный вывод.

Формат[править]

Объявление формата[править]

Формат — это особая языковая единица Perl, которая определяет, как должна быть отформатирована каждая выводимая на устройство вывода строка. Вывод по формату осуществляется с помощью функции write().

Формат объявляется с помощью ключевого слова format следующим образом:

format <имя-формата> = 
<сам-формат-оформленный-по-правилам>
.

# Формат заканчивается символом точки.

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

Имя формата обычно объявляется в верхнем регистре (как дескрипторы), хотя язык этого не требует. Это нужно для того, чтобы иметь возможность ассоциировать их с дескрипторами, так как функции write(), может быть передан дескриптор имя которого должно совпадать с форматом. Когда write() вызывается без аргумента, то используется дескриптор, установленный функцией select(). По умолчанию это STDOUT. Таким образом, имя формата для STDOUT должен иметь это же имя, чтобы происходил форматированный вывод.

Perl позволяет выводить данные постранично. Для такого вывода можно объявлять формат для верхнего колонтитула, который будет повторяться на каждой странице. Такой формат всегда должен иметь суффикс _TOP на конце имени, например, STDOUT_TOP.

Составление формата[править]

Формат строки состоит минимум из одной-двух строк:

  • первая строка (строка шаблонов) — собственно сам шаблон;
  • вторая строка представляет строку переменных, значения которых подставляются в поля шаблона.

Строка шаблонов печатается в точности так, как она выглядит в тексте программы (включая пробельные символы). Некоторые символы в строке шаблонов несут особый смысл и определяют форматирование в поле.

  • > — определяет символьное поле, в котором символы выравниваются по правому краю;
  • < — определяет символьное поле, в котором выводимое значение выравнивается по левому краю;
  • | — определяет символьное поле, в котором выводимое значение выравнивается по центру;
  • # — определяет поле, в которое выводится число;
  • . — определяет положение десятичной точки в числовом поле.

Поле в форматной строке обозначается одним из двух символов (@ или ^), после которого идут символы форматирования. Все символы форматирования, включая символ начала поля, определяют количество знакомест, которые будут тратиться на его вывод.

Символ ^ определяет поля, в которых символы могут переноситься на новую строку, когда не хватает места в поле на текущей строке. Мы рассмотрим как работать с этим в примерах.

С форматированным выводом ассоциировано несколько встроенных переменных:

  • $~ — имя формата по умолчанию, которое используется, когда write() вызывается без аргумента.
  • $% — номер текущей страницы.
  • $= — число строк, выводимых на одной странице.

Примеры использования форматов[править]

Начнем со следующего комплексного примера.

#!/usr/bin/perl

format STDOUT_TOP =
@<<<<<<<<<<<
"Page " . "#" . $%
+==============+==============+==============+==============+
|     left     |    center    |    right     |    number    |
+==============+==============+==============+==============+
.

format STDOUT=
|@<<<<<<<<<<<<<| @||||||||||| |@>>>>>>>>>>>>>|      @####.##|
$field1, $field2, $field3, $field4
.

format BOTTOM =
+--------------+--------------+--------------+--------------+

TOTAL LINES: @<<<<<
$count_lines
.

$= = 10;

while (($field1, $field2, $field3, $field4) = split(/,/, <DATA>)) {
    ++$count_lines;
    write();
}
$~ = BOTTOM;
write;

__DATA__
string,string,string,3.141595
string,string,string,1235.5484
string,string,string,36589.5123
string,string,string,19564
string,string,string,10000
string,string,string,2000
string,string,string,5500
string,string,string,80000
string,string,string,95423
string,string,string,95423
string,string,string,95423
string,string,string,95423
string,string,string,95423
string,string,string,95423
string,string,string,95423
string,string,string,95423
string,string,string,95423
string,string,string,95423
string,string,string,95423

Результат работы этой программы представлен ниже.

Page #1
+==============+==============+==============+==============+
|     left     |    center    |    right     |    number    |
+==============+==============+==============+==============+
|string        |    string    |        string|          3.14|
|string        |    string    |        string|       1235.55|
|string        |    string    |        string|      36589.51|
|string        |    string    |        string|      19564.00|
|string        |    string    |        string|      10000.00|
|string        |    string    |        string|       2000.00|
Page #2
+==============+==============+==============+==============+
|     left     |    center    |    right     |    number    |
+==============+==============+==============+==============+
|string        |    string    |        string|       5500.00|
|string        |    string    |        string|      80000.00|
|string        |    string    |        string|      95423.00|
|string        |    string    |        string|      95423.00|
|string        |    string    |        string|      95423.00|
|string        |    string    |        string|      95423.00|
Page #3
+==============+==============+==============+==============+
|     left     |    center    |    right     |    number    |
+==============+==============+==============+==============+
|string        |    string    |        string|      95423.00|
|string        |    string    |        string|      95423.00|
|string        |    string    |        string|      95423.00|
|string        |    string    |        string|      95423.00|
|string        |    string    |        string|      95423.00|
|string        |    string    |        string|      95423.00|
Page #4
+==============+==============+==============+==============+
|     left     |    center    |    right     |    number    |
+==============+==============+==============+==============+
|string        |    string    |        string|      95423.00|
+--------------+--------------+--------------+--------------+

TOTAL LINES: 19

В этом примере было объявлено три формата:

  • STDOUT_TOP — формат для вывода верхнего колонтитула на каждой новой странице.
  • STDOUT — формат для вывода очередной строки.
  • BOTTOM — формат для вывода итоговой строки.

В этой программе мы читаем данные из секции __DATA__, разбиваем каждую строку на 4 поля и отдаем их формату. Имена переменных, из которых нужно брать данные фигурируют в формате STDOUT. Форматная строка этого формата составлена так, чтобы имитировать печать данных таблицей. Первое поле форматной строки выравнивает данные по левому краю, второе поле — по центру, и третье поле выравнивает данные по правому краю. В четвертом поле выводится число, причем ожидается 4 цифры в целой части и две в дробной. Тем не менее, если знакомест недостаточно для вывода числа целиком, то система вывода отходит от формата, что можно видеть в итоговом результате.

С помощью переменной $= мы определили, что на одной странице выводится не более 10 строк, включая верхний колонтитул. Как мы видим, все данные помещаются при такой настройке на 4-х страницах. Колонтитул верхнего формата настроен так, чтобы выводить номер очередной страницы.

В Perl нет отдельного формата для представления нижнего колонтитула, поэтому мы составили его сами. Когда цикл отрабатывает, мы переключаемся на формат BOTTOM с помощью переменной $~ и выводим по этому формату через вызов write().

В этом примере не показано, но что будет, если число определенных в поле знакомест не хватит для вывода значения в него? Для поля, определенного через @, значение будет просто обрезано:

format STDOUT =
@<<<<<<<<<<
$string
.

$string = "A very very very long line";

write;

Результат

A very very

В данном случае нужно объяснить Perl, что если данные не помещаются в поле на текущей строке, то не помещающиеся данные нужно разместить в этом же поле на следующей строке. Для этого поле должно быть объявлено через ^. Соответственно нужно объявлять поля с возможным переносом на новую строку так:

format STDOUT =
^<<<<<<<<<<
$string
^<<<<<<<<<<~
$string
.

$string = "A very very very long line";

write;

Результат

A very very
very long

Знак ~ в конце строки подавляет вывод пустой строки между двумя форматами. Обратите внимание, что даже так строка не влезает целиком. Чтобы сказать Perl, что нужно переносить значения до тех пор, пока данные не вместятся полностью, нужно поставить два знака ~:

format STDOUT =
^<<<<<<<<<<
$string
^<<<<<<<<<<~~
$string
.

$string = "A very very very long line";

write;

Результат

A very very
very long  
line

Чтобы сказать полю, что оно может использовать всю оставшуюся ширину строки на устройстве вывода, можно использовать формат *:

format STDOUT =
@*
$string
.

$string = "A very very very long line";

write;

Результат

A very very very long line

Форматированный вывод с помощью printf[править]

Форматированный вывод доступен и в форме единичных вызовов с помощью функций printf и sprintf. Первая из них служит для вывода форматированной строки на устройство вывода или в файл, а вторая — для записи форматированной строки в переменную.

printf [<дескриптор>] <форматная-строка>, <список>;
sprintf <форматная-строка>, <список>;

В обеих функциях ключевым элементом является форматная строка, которая определяет поля и правила их заполнения. Форматная строка заполняется форматами, начинающимися со знака %, после которого идет символ, объясняющий какой тип данных выводится в поле, и правила форматирования строки в поле. В основном форматы скопированы из одноименных функций из стандартной библиотеки Си, но в Perl есть еще несколько расширений.

Ниже перечислены все типы полей для форматов:

  • %% — знак процента;
  • — единичный символ;
  • %s — строка;
  • %d — знаковое десятичное целое число;
  • %u — беззнаковое десятичное целое число;
  • %o — беззнаковое целое число в восьмеричной системе счисления;
  • %x — беззнаковое целое число в шестнадцатеричной системе счисления;
  • %e — вещественное число в экспоненциальной форме;
  • %f — вещественное число в виде десятичной дроби;
  • %g — представление в виде %e или %f в зависимости от величины;
  • %X — как %x, но в верхнем регистре;
  • %E — как %e, но символ экспоненты будет представлен в верхнем регистре;
  • %G — как %g, но выбор будет между %f и %E;
  • %b — беззнаковое целое число в двоичной системе счисления;
  • %B — как %b, но вместе с # префикс 0B выводится с B в верхнем регистре;
  • %p — указатель; для вывода адресов в шестнадцатеричной форме;
  • %n — сохраняет число выведенных символов в следующий аргумент списка вывода;
  • %a — шестнадцатеричное представление вещественного числа;
  • %A — как %a, но в верхнем регистре.

Следующие форматы используются как синонимы:

  • %i — синоним %d;
  • %D — синоним %ld;
  • %U — синоним %lu;
  • %O — синоним %lo;
  • %F — синоним %f.

Между знаком процента и символом типа данных в формате можно вставлять дополнительные символы, уточняющие форматирование поля.

  • пробел — для положительных чисел оставляет пробел на месте знака для тонкого выравнивания в поле;
  • + — для положительных чисел всегда подставляет знак плюс при выводе числа. Для тонкого выравнивания в поле;
  • - — для выравнивания по левому краю в поле;
  • 0 — использовать нули вместо пробелов для выравнивания по правому краю;
  • # — для форматов %o, %x и %b вставляет префиксы соответственно 0, 0x и 0b. Для форматов %O, %X и %B префиксы подставляются в верхнем регистре.
  • число — минимальная ширина поля. Оставшиеся знакоместа будут заполняться пробелом. Если значение имеет больше символов чем минимальная ширина поля, то будут выделяться дополнительные. Если использовать символ *, то ширину поля можно указать следующим аргументом.
  • число после точки — для форматов выражающих вещественные числа, кроме %g и %G, определяет сколько знакомест использовать для вывода дробной части (по умолчанию 6), или, другими словами, выражает точность. Для всех остальных форматов означает максимальную ширину поля.

Для примеров мы будем использовать функцию printf, но имейте в виду, что для sprintf все аналогично.

# Типы данных
printf '<% d>',  12;   # "< 12>"
printf '<% d>',   0;   # "< 0>"
printf '<% d>', -12;   # "<-12>"
printf '<%+d>',  12;   # "<+12>"
printf '<%+d>',   0;   # "<+0>"
printf '<%+d>', -12;   # "<-12>"
printf '<%6s>',  12;   # "<    12>"
printf '<%-6s>', 12;   # "<12    >"
printf '<%06s>', 12;   # "<000012>"
printf '<%#o>',  12;   # "<014>"
printf '<%#x>',  12;   # "<0xc>"
printf '<%#X>',  12;   # "<0XC>"
printf '<%#b>',  12;   # "<0b1100>"
printf '<%#B>',  12;   # "<0B1100>"

# Минимальная ширина
printf "<%s>", "a";       # "<a>"
printf "<%6s>", "a";      # "<     a>"
printf "<%*s>", 6, "a";   # "<     a>"
printf '<%*2$s>', "a", 6; # "<     a>"
printf "<%2s>", "long";   # "<long>" (недостающие знакоместа добавляются)

# Точность для вещественных чисел
printf '<%f>', 1;    # "<1.000000>"
printf '<%.1f>', 1;  # "<1.0>"
printf '<%.0f>', 1;  # "<1>"
printf '<%e>', 10;   # "<1.000000e+01>"
printf '<%.1e>', 10; # "<1.0e+01>"

# Максимальная ширина поля
printf '<%g>', 1;        # "<1>"
printf '<%.10g>', 1;     # "<1>"
printf '<%g>', 100;      # "<100>"
printf '<%.1g>', 100;    # "<1e+02>"
printf '<%.2g>', 100.01; # "<1e+02>"
printf '<%.5g>', 100.01; # "<100.01>"
printf '<%.4g>', 100.01; # "<100>"
printf '<%.1g>', 0.0111; # "<0.01>"
printf '<%.2g>', 0.0111; # "<0.011>"
printf '<%.3g>', 0.0111; # "<0.0111>"

# Комбинирование минимальной и максимальной ширины
printf '<%.6d>', 1;      # "<000001>"
printf '<%+.6d>', 1;     # "<+000001>"
printf '<%-10.6d>', 1;   # "<000001    >"
printf '<%10.6d>', 1;    # "<    000001>"
printf '<%010.6d>', 1;   # "<    000001>"
printf '<%+10.6d>', 1;   # "<   +000001>"
printf '<%.6x>', 1;      # "<000001>"
printf '<%#.6x>', 1;     # "<0x000001>"
printf '<%-10.6x>', 1;   # "<000001    >"
printf '<%10.6x>', 1;    # "<    000001>"
printf '<%010.6x>', 1;   # "<    000001>"
printf '<%#10.6x>', 1;   # "<  0x000001>"

# Для строковых значений максимальная ширина будет обрезать значение в поле
printf '<%.5s>', "truncated";   # "<trunc>"
printf '<%10.5s>', "truncated"; # "<     trunc>"

# Вынос точности за форматную строку
# Примечание: если точность отрицательная, то формат вычисляется как будто ее нет.
printf '<%.6x>', 1;       # "<000001>"
printf '<%.*x>', 6, 1;    # "<000001>"
printf '<%.*2$x>', 1, 6;  # "<000001>"
printf '<%6.*2$x>', 1, 4; # "<  0001>"
printf '<%.*s>', -1, "string";   # "<string>" (нет эффекта)

# Для каждого формата точность указывается отдельно
printf '<%.*x> <%.*x>', 6, 1, 8, 2; # <000001> <00000002>
# Подходы можно смешивать
printf '<%.6x> <%.*x>', 1, 8, 2; # <000001> <00000002>

Обычно форматы записываются в порядке вывода переменных в списке, но при желании порядок вывода можно менять с помощью специального синтаксиса в формате, например 2$, где 2 — номер элемента в списке.

printf '%3$d %d %1$d', 1, 2, 3;  # "3 1 1"
# Во втором формате будет выведена 1 (первый элемент списка) из-за вмешательства в порядок.

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

Еще одну возможность вывода предоставляет векторный формат %v, который выводит символы в строке в виде серии кодов символов, разделенных по умолчанию точкой. Коды могут быть записаны в десятеричной, двоичной и других системах счисления.

printf "%vd", "ABcdEf"; # "65.66.99.100.69.102"
printf "%vb", "ABcdEf"; # "1000001.1000010.1100011.1100100.1000101.1100110"

# Разделитель может быть по желанию изменен
printf "%*vX\n", ":", "ABcdEf"; "41:42:63:64:45:66"

# Комбинирование
printf "%0*v8b\n", " ", "123456"; # "00110001 00110010 00110011 00110100 00110101 00110110"

Форматированный вывод работает намного медленнее, чем print и ей подобные функции. Отдавайте предпочтение print, когда вам нужна простая печать.



← Ввод и вывод Ссылки →