Next Previous Contents

Unix Review Column 32 -- Некоторые приемы работы c printf

Randal Schwartz

Июнь 2000

[Предполагаемый заголовок: Пусть это выглядит так, как вы захотите]

Перевод Anton Petrusevich <casus@mail.ru> и Alex Ott <ott@phtd.tpu.edu.ru>

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

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


  printf "my string %s has %d characters.\n", $str, length($str);

Здесь поле %s выдает символьное значение, которое находится в переменной $str. Аналогичным образом поле %d выдает десятичное значение, вычисленное операцией length($str). Параметры оцениваются в списочном контексте, так что мы могли бы использовать следующий код для достижения тех же результатов:


  @output = ($str, length($str));
  printf "my string %s has %d characters.\n", @output;

Задача становится интересной, если мы не знаем длину массива @output, поскольку нам необходимо иметь поле % для каждого из элементов массива @output, но поскольку мы сами создали массив, то здесь нет никаких проблем.

Кроме %s для строк и %d для десятичных целых чисел, другим часто применяемым форматом является %f для чисел с плавающей запятой:


  printf "he has $%f in his account\n", 3.50;

Здесь значение 3.5 выдается как число с плавающей точкой 3.500000. Но почему появились лишние нули? По умолчанию точность для чисел с плавающей запятой равна 6 символам после десятичной точки. Для уменьшения этого значения, мы можем управление точностью в формат, поместив нужное число между % и f.


  printf "he has $%.2f in his account\n", 3.50;

И это выдаст нам 3.50, как мы и ожидали. Здесь 2 означает, что будет выводиться дву цифры, что станет означать центы. Для того, чтобы вместиться в заданный формат, число округляется, так что 3.509 должно показываться как 3.51, а 3.502 будет выведено как 3.50. В качестве крайнего случая мы можем использовать %.0f для округления до ближайшего целого числа, и десятичная точка не будет использоваться.

Другим общим форматом в научной нотации является %e. Он полезен того, когда число может быть слишком большим для представления несколькими цифрами:


  printf "2 to the 100 power is approximately %e\n", 2 ** 100;

Это выдаст нам 1.267651e+30, снова используя 6 цифр после десятичной точки, до тех пор пока мы не будем явно задавать точность, например как %.10e.

Но %e редко используется (как я заметил). В общем случае, когда выдается число неизвестной величины или точности, то большинство программистов возвращаются к использованию %g ``общему формату чисел''. В этом случае, число форматируется используя либо %d, либо %f, либо %e, в зависимости от того, какой из форматов дает ``лучший'' результат. Если число является целым, то будет использован формат для целых чисел. Если число с плавающей точкой имеет разумную величины, то используется обычный формат, а в противном случае будет использоваться научный вид записи чисел.


  printf "Your number is %g\n", $number;

Снова может быть использовано указание точности, но в этом случае это поле указывает максимальное количество значащих цифр, со значением по умолчанию равным 6. Так что для %.15g, мы получим наилучшее отображение 15 наиболее значащих цифр.

Для строк, мы получаем аналогичный контроль ``точности''. Если мы включаем точности для строки, а строка длинее заданного числа, то она автоматически сокращается:


  printf "I said %.5s!\n", "hello world";

что выдает I said hello!, обрезая строку.

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


  printf "=%10s=\n", "hello";

Здесь строка из символов не заполняет все 10 символов, так что слева будет добавлено 4 пробела. Здесь указывается минимальная, а не максимальная ширина поля, так что если строка будет длинее, то она будет выдана полностью. Мы можем объединить поле указания точности с полем указания ширины, для того, чтобы получить строку, которая дополнена до заданного размера, или обрезана, если строка превышает заданный размер. Рассмотрим простой код:


  printf "=%5.5s=\n", substr("1234567890", 0, $_) for 0..10;

что выдает нам великолепную модель:


    =     =
    =    1=
    =   12=
    =  123=
    = 1234=
    =12345=
    =12345=
    =12345=
    =12345=
    =12345=
    =12345=

Дополнение пробелами может происходить справа, а не слева, при использовании отрицательного числа в качестве минимальной ширины:


  printf "=%-5.5s=\n", substr("1234567890", 0, $_) for 0..10;


    =     =
    =1    =
    =12   =
    =123  =
    =1234 =
    =12345=
    =12345=
    =12345=
    =12345=
    =12345=
    =12345=

Также числа могут дополняться нулями, а не пробелами, если использовать знак 0 в начале ширины:


  printf "%02d:%02d:%02d %s", $h, $m, $s, $ampm;

Если число в $m меньше чем 10 (например 7), то мы получим в выводе ведущий 0 (например 07), что очень полезно для отображения времени, как в нашем случае.

Знак % может быть получен его дублированием, как в коде:


  printf "He scored %.0f%% of the goals", 100 * $him / $total;

Заметьте, что часто предпринимаемое маскирование % с помощью символа обратный слэш, не будет работать. Это не проблема интерполяции строк: это проблема интерпретации в printf.

Одним, из менее часто используемых форматов является ``символьный'' формат:


  printf "the letter A is %c\n", 65;

Здесь, значение 65 рассматривается как код ASCII, и превращается в символ ``A''. Этот формат не так часто используется в Perl, в отличии от C, поскольку Perl связан со строками как типами данных первого класса, редко раскрывая программисту численные значения индивидуальных символов.

И имеются форматы ``типов для программиста'' ... %h для шестнадцатеричного вывода, %o для восьмеричного, и новый формат в Perl 5.6, %b для двоичного вывода. Например, вот один из способов для того, чтобы показать права доступа к файлу:


        printf "%s is mode %o\n", $_, 07777 & (stat)[2] for @ARGV;

Но глядя на вывод, значения искажают все вокруг. Нужно использовать поле минимальной длины:


        printf "%30s is mode %04o\n", $_, 07777 & (stat)[2] for @ARGV;

Это основы, но давайте также взглянем на некоторый практический код.Предположим, что у меня есть набор значений в массиве @numbers, которые я хочу напечатать в виде вертикальной колонки с форматом %15g для всех чисел. Вы можете подумать, что я могу просто сделать следующее:


        printf "%15g\n", @numbers; # bad

Но этот код не будет работать, поскольку поле % необходимо для каждого из значений списка (как мы увидели ранее). Хорошо, простым способом исправления этой проблемы является использовании цикла:


        printf "%15g\n", $_ for @numbers;

Но другим способом является репликация строки формата. Если нам необходимо выдать три записи, что нам нужна строка %15g\n%15g\n%15g\n, которую мы можем получить с помощью операции "%15g\n" x 3. Так что нам необходимо знать число элементов в массиве @numbers для использования в правой части оператора x. Это достаточно просто: просто используйте имя массива в скалярном контексте (который здесь и используется!):


        printf "%15g\n" x @numbers, @numbers;

Здесь массив @numbers используется и в скалярном и в списочном контексте в одном выражении: один и тот же текст, но с разным значением.

Время от времени, вам может понадобиться переменная дина колонки. Например вам может понадобиться, чтобы число 15 из предыдущего примера было настраиваемым:


        $width = 15;
        printf "%$widthg\n", $_ for @numbers; # bad

Этот код не работает, поскольку Perl ищет переменную с именем $widthg, даже если вы использовали имя переменной $width за которой следует g. Но вы также не можете помести здесь пробел, поскольку формат для printf разборчив и не понимает пробелов. Одним из решений является отделение имени переменной:


        $width = 15;
        printf "%${width}g\n", $_ for @numbers;

Другим способом является использование в списке * для того, чтобы определить число. Каждое появление * в формате поля заставляет использовать один элемент из списка значений для численного значения, которое обозначает *:


        $width = 15;
        printf "%*g\n", $width, $_ for @numbers;

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


Next Previous Contents