Перейти к содержанию

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

Материал из Викиучебника — открытых книг для открытого мира

Форматированный вывод


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

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

Формат

[править]

Объявление формата

[править]

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

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

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

# Формат заканчивается символом точки.
# Если имя формата опущено, то подразумевается STDOUT.

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

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

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

Составление формата

[править]

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

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

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

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

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

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

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

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

Эти переменные нужно устанавливать для каждого отдельно взятого дескриптора одним из следующих способов:

# Однострочная форма
select((select(HANDLE),
   $~ = "FORMAT_NAME",
   $^ = "FORMAT_NAME_TOP"
)[0]);

# Развернутая форма
$prev_handle = select(HANDLE);
$~ = "FORMAT_NAME";
$^ = "FORMAT_NAME_TOP";
select($prev_handle);

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

[править]

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

#!/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

Еще один пример с форматами

[править]

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

Иногда администратору нужно получить отчет по созданным в системе учетным записям пользователей и группам пользователей. В системах *nix такая информация хранится соответственно в файлах /etc/passwd и /etc/group. В файле /etc/passwd запись представлена в формате <username>:<password>:<UID>:<GID>:<GECKOS>:<HOME>:<SHELL>, где <username>:<password> — пользователь и пароль[2]; <UID>:<GID> — идентификатор пользователя и идентификатор главной группы пользователя; <GECKOS> — можно сказать, что это комментарий, который раскрывает информацию о том, зачем используется эта учетная запись; <HOME>:<SHELL> — домашняя директория и командная оболочка по умолчанию.

Файл /etc/group устроен проще: <groupname>:<password>:<GID>:<LIST_OF_MEMBERS>, где <groupname>:<password> — имя группы и пароль группы; <GID> — идентификатор группы; <LIST_OF_MEMBERS> — список пользователей, которые находятся в группе.

Хотя файлы устроены очень просто, просматривать их глазами неудобно, особенно если не знаешь/не помнишь структуру записи. С помощью Perl можно вывести каждый из этих файлов форматированной таблицей, которая уже больше будет похожа на отчет. Программа ниже демонстрирует, как можно создать такой отчет с помощью форматов. Код следующего примера написан с помощью некоторых языковых конструкций, которые будут подробно рассмотрены в более поздних главах. Главное в этом примере это форматы.

#!/usr/bin/perl
# Файл: users.pl

use strict;
use warnings;

use Getopt::Long qw ( GetOptions );

use subs qw ( print_users print_groups );

our ($passwd, $groups) = qw ( /etc/passwd /etc/group );
our ($username, $password, $uid, $gid, $geckos, $home, $groupname, $members, $shell);

my $isGroupsPrinting;

GetOptions (
	'groups' => \$isGroupsPrinting,
) or die "Invalid call";

$isGroupsPrinting ? print_groups : print_users;

sub print_users {
	open (PASSWD, '<', our $passwd)
		or die "Cannot open '$passwd': $!";
	local $~ = 'PASSWD';
	local $^ = 'PASSWD_TOP';
	while (my $line = <PASSWD>) {
		our ($username, $password, $uid, $gid, $geckos, $home, $shell) = split /:/, $line;
		write;
	}
	close PASSWD;
}

sub print_groups {
	open (GROUPS, '<', our $groups)
		or die "Cannot open '$groups': $!";
	local $~ = 'GROUPS';
	local $^ = 'GROUPS_TOP';
	while (my $line = <GROUPS>) {
		our ($groupname, $password, $gid, $members) = split /:/, $line;
		write;
	}
	close GROUPS;
}

format PASSWD_TOP =

Username               UID    GID    Home Directory       Shell                Others
---------------------  -----  -----  -------------------  -------------------  ---------------------------------
.
format PASSWD =
@<<<<<<<<<<<<<<<<<<<<  @<<<<  @<<<<  @<<<<<<<<<<<<<<<<<<  @<<<<<<<<<<<<<<<<<<  ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$username,             $uid,  $gid,  $home,               $shell,              $geckos
                                                                               ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
																			   $geckos
.
format GROUPS_TOP =

Groupname              GID    Members
---------------------  -----  ---------------------------------
.
format GROUPS =
@<<<<<<<<<<<<<<<<<<<<  @<<<<  ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$groupname,            $gid,  $members
                              ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
							  $members
.

Если вызывать программу без аргументов, то выводится будет список пользователей, а если использовать опцию -group (сокр. -g), выводится будет список групп.

$ ./users.pl

Username               UID    GID    Home Directory       Shell                Others
---------------------  -----  -----  -------------------  -------------------  ---------------------------------
root                   0      0      /root                /bin/bash            root
daemon                 1      1      /usr/sbin            /usr/sbin/nologin    daemon
bin                    2      2      /bin                 /usr/sbin/nologin    bin
sys                    3      3      /dev                 /usr/sbin/nologin    sys
sync                   4      65534  /bin                 /bin/sync            sync
games                  5      60     /usr/games           /usr/sbin/nologin    games
man                    6      12     /var/cache/man       /usr/sbin/nologin    man
lp                     7      7      /var/spool/lpd       /usr/sbin/nologin    lp
mail                   8      8      /var/mail            /usr/sbin/nologin    mail
news                   9      9      /var/spool/news      /usr/sbin/nologin    news
uucp                   10     10     /var/spool/uucp      /usr/sbin/nologin    uucp
proxy                  13     13     /bin                 /usr/sbin/nologin    proxy
www-data               33     33     /var/www             /usr/sbin/nologin    www-data
backup                 34     34     /var/backups         /usr/sbin/nologin    backup
list                   38     38     /var/list            /usr/sbin/nologin    Mailing List Manager
irc                    39     39     /run/ircd            /usr/sbin/nologin    ircd
_apt                   42     65534  /nonexistent         /usr/sbin/nologin
nobody                 65534  65534  /nonexistent         /usr/sbin/nologin    nobody
systemd-network        998    998    /                    /usr/sbin/nologin    systemd Network Management
tss                    100    107    /var/lib/tpm         /bin/false           TPM software stack,,,

<---------- вывод обрезан ---------->

$ ./users.pl -g

Groupname              GID    Members
---------------------  -----  ---------------------------------
root                   0      john
daemon                 1      opensearch
bin                    2
sys                    3
adm                    4
tty                    5
disk                   6
lp                     7
mail                   8
news                   9
uucp                   10
man                    12
proxy                  13
kmem                   15
dialout                20
fax                    21
voice                  22
cdrom                  24
floppy                 25

<---------- вывод обрезан ---------->

В этой программе используется четыре формата, расположенных в конце исходного файла. Форматы PASSWD_TOP и PASSWD используются для табличной печати списка пользователей из файла /etc/passwd, а форматы GROUPS_TOP и GROUPS/etc/group.

Чтобы уместить обе возможности в одной программе, мы написали две процедуры (print_users и print_groups), которые вызываются в зависимости от установленного флага $isGroupsPrinting. Чтобы иметь возможность переключать этот флаг из командной оболочки, мы используем модуль Getopt::Long, который парсит командную строку в поисках опции -groups и инициализирует $isGroupsPrinting значением 1, если эта опция встретилась в команде.

Хотя обе процедуры печатают разные файлы, они делают это очень похоже. В коде процедур следует обратить внимание на то, как переключается формат. Мы это делаем предопределенными переменными $~ и $^, через которые устанавливаются форматы соответственно для печати по вызову write и верхнего колонтитула. Формат верхнего колонтитула нужно в данном случае указывать явно, потому что по умолчанию используется STDOUT_TOP, а наш дескриптор открыт на чтение, а не на запись. Оставшаяся часть каждой процедуры представляет цикл построчного чтения файла, где мы из каждой строки извлекаем строку, разбиваем её на поля по разделителю : и инициализируем вычитанными значениями нужные нам переменные, которые будут передаваться выбранному формату. Далее мы вызываем write, которая применяет выбранный формат и печатает результат в STDOUT.

Этот простой пример наглядно показывает, как можно выводить данные таблицей, но на самом деле вы не ограничены только таблицами. Вы могли бы выводить записи в любой форме, так как формат может быть многострочным. Раньше, когда на Perl писали CGI скрипты для первых WEB-сайтов, очень часто таким способом выводился однотипный код для HTML-списков или строк HTML-таблиц.

Форматированный вывод с помощью 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, когда вам нужна простая печать.

Примечания

[править]
  1. Запись по формату в таком виде была заимствована из языка FORTRAN.
  2. На самом деле пароль в этом файле уже давно не хранится.