Next Previous Contents

Unix Review Column 1 -- Простой разбор данных

Randal Schwartz

март 1995

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

Perl быстро стал ключевым инструментом в багаже типичного системного администратора и системного программиста.

Тем не менее, легко испугаться при виде 211 страниц печатной документации, которые идут с последней версией Perl (версия 5). У вас могут возникнуть вопросы: ``С чего начать?'' и ``Сколько из этого надо знать, чтобы писать на Perl?''

Один из самых простых способов научиться --- смотреть, как кто-нибудь другой решает элементарную задачу. Например, возьмем типичную задачу администрирования --- присвоение уникального номера (userID) новому пользователю. Для этого, мы должны узнать текущий максимальный номер пользователя(userID), и затем выбрать следующий максимальный номер.

Мы разобьем задачу на мелкие подзадачи и будем их решать.

Сначала рассмотрим печать первой колонки данных, выдаваемых командой who


who | perl -ne '@F = split; print ''$F[0]\n'';'

Выходной поток who становится входным потоком для Perl. Ключ -n заставляет Perl исполнять некоторый код строчка за строчкой. Каждая входная строчка помещается в переменную $_. Ключ -e указывает код, который будет исполнять Perl. Ключи можно комбинировать, как это сделали мы.

В нашем случае есть два Perl выражения: операция split, и print. Операция split разбивает содержимое $_, создавая список слов (считаем пробелы разделителями слов). Список помещается в массив @F. Затем операция print печатает содержимое первого элемента массива @F и сразу перевод строки (\n). Заметим, что первый элемент массива имеет номер 0, подобно массивам в Си.

Можно немного урезать код, удалив split:


who | perl -ane 'print ''$F[0]\n'' '

Заметим добавление ключа -a, заставляющего Perl автоматически выполнить split и поместить результат в массив @F.

Чтобы укоротить строчку еще больше, добавим ещё ключ -l, который делает две вещи сразу:

В результате получаем:


who | perl -lane 'print $F[0]'

Чтобы еще сократить код, заменим ключ -n на -p, который заставляет Perl печатать содержимое, оставшееся в $_:


who | perl -lape '$_ = $F[0]'

Аналогичный по функциональности Perl-скрипт выглядит так:


        #!/usr/bin/perl
        $\ = $/;                # from -l
        while (<>) {            # from -p
                chop;           # from -l
                @F = split;     # from -a
                $_ = $F[0];     # argument to -e
                print;          # from -p
        }

Здесь видно, какое количество текста сократили нам ключи в командной строке Perl.

Переменная $\ задаёт терминатор-суффикс для каждой операции print, подобно переменной ORS в Awk. По умолчанию эта переменная пуста, что позволяет вам самостоятельно контролировать формат выводимой print информации.

Здесь мы устанавливаем переменную $\ равной значению переменной $/, которая является разделителем полей ввода (подобно RS в Awk). По умолчанию, её значение равно '\n'. Таким образом, мы добиваемся перевода на новую строчку после каждой выведенной строки.

Теперь от команды who перейдём к реальной задаче: разбор файла паролей для поиска наибольшего номера пользователя (userID).

Файл паролей отличается от того, что выдаёт who тем, что колонки разделены не пробелами, а двоеточиями. Нет проблем --- сообщим об этом Perl:


perl -F: -lane 'print $F[0]' /etc/passwd 

С помощью ключа -F мы задали новый разделитель колонок и в результате получили список всех пользователей в нашей системе.

Если у нас запущена служба ``Жёлтых Страниц'' (Yellow Pages), то вам, возможно, надо будет другим образом получить информацию о пользователях:


ypcat passwd | perl -F: -lane 'print $F[0]' 

Здесь команда ypcat выдает в поток стандартного вывода файл похожий на passwd, который может быть обработан Perl так же как и локальный /etc/passwd.

Во всех наших упражнениях мы получали только имена пользователей, а не их номера (userID). Номер пользователя (userID) находится в третьей колонке, или $F[2] в нашем скрипте.


perl -F: -lane 'print $F[2]' /etc/passwd 

Мы получили список номеров, теперь надо определить максимальный и напечатать следующий максимальный номер.

Для этого заведём скалярную переменную $max. Изначально, $max не определена, она будет равняться нулю в сравнениях с числами. Наша работа состоит в сравнении каждого номера пользователя (userID) с $max и установке $max в значение номера, если он больше.


perl -F: -lane '$max = $F[2] if $max < $F[2]; \
       print $max' /etc/passwd 

Здесь мы присваиваем $max значение $F[2], как только выполнилось условие


$max < $F[2]

Это происходит на каждом шаге цикла по строчкам файла


/etc/passwd

. Это одно из мест в Perl, где логическое условие размещается слева, а не справа.

Изобразим это равнозначным скриптом:


        #!/usr/bin/perl
        $\ = $/;
        while (<>) {
                chop;
                @F = split /:/;
                $max = $F[2] if $max < $F[2];
                print $max;
        }

Это уже почти то, что нам надо. Однако нам все еще надо передавать данные из /etc/passwd в скрипт, что не совсем удобно. Давайте внесем открытие файла /etc/passwd внутрь скрипта.


        #!/usr/bin/perl
        open(PASSWD,"/etc/passwd");
        $\ = $/;
        while (<PASSWD>) {
                chop;
                @F = split /:/;
                $max = $F[2] if $max < $F[2];
                print $max;
        }

Директива open создаёт дескриптор файла, ассоциированный с /etc/passwd для чтения.

Если у вас используются Yellow Pages, то для этого случая решение чуть-чуть длиннее:


        #!/usr/bin/perl
        open(PASSWD,"ypcat passwd|");
        $\ = $/;
        while (<PASSWD>) {
                chop;
                @F = split /:/;
                $max = $F[2] if $max < $F[2];
                print $max;
        }

Здесь Perl использует вместо файла вывод команды. Признаком команды является вертикальная черта в конце. Это напоминает оператор перенаправления, который мы использовали при написании command-line версии программы.

Вывод наших последних нескольких программ представлял собой серию чисел, представляющих максимальный userID, который мы находили на каждом шаге. На самом же деле, нам нужно только последнее число. Как это сделать в программе? Просто вынесем print из цикла.


        #!/usr/bin/perl
        open(PASSWD,"/etc/passwd"); # or YP equivalent
        $\ = $/;
        while (<PASSWD>) {
                chop;
                @F = split /:/;
                $max = $F[2] if $max < $F[2];
        }
        print $max + 1;

Не забудем прибавить 1, чтобы получить число большее, чем предыдущее максимальное.

Теперь сохраним этот скрипт в файле, поставим на нём бит исполнимости и поместим его куда-нибудь в $PATH. Потом, когда нам понадобится новый номер пользователя, просто вызовем этот скрипт, заключив его имя в обратные кавычки, и получим правильный номер.

Или почти правильный номер. Некоторые системы (типа SunOS, на которых это тестировалось), имеют пользователя nobody, который имеет максимально возможный номер, например, 65535. Если мы запустим нашу программу, то получим не то, что хотели.

Поступим просто -- исключим большие номера, например, большие 30000, т.е. $max будет изменяться только если $F[2] меньше 30000. Нам всего лишь придётся усложнить правую часть if:


        #!/usr/bin/perl
        open(PASSWD,"/etc/passwd"); # or YP equivalent
        $\ = $/;
        while (<PASSWD>) {
                chop;
                @F = split /:/;
                $max = $F[2] if $F[2] < 30000 and $max < $F[2];
        }
        print $max + 1;

Вот, с этой задачей покончили. Как минимум, она работает на SunOS.

Так что решение этой маленькой задачи получилось не таким уж маленьким, но по крайней мере мы выполнили ее с помощью нескольких строк на Perl. Теперь, если вы не боитесь длинных командных строк, то давайте переведём эту программу в форму командной строки:


perl -aF: -lne '$m=$F[2] if $F[2]<30000 and $m<$F[2];\
          END { print $m+1 }' /etc/passwd

Здесь интересным свойством является то, что блок END автоматически перемещается за пределы неявного цикла, помещая его там, где он находится в скрипте.

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

Learning Perl (O'Reilly and Associates, ISBN 1-56592-042-2) постепенное введение в язык, вместе с упражнениями и прокомментированными ответами. Рассчитана на людей ``знакомых с UNIX, но никоим образом не Гуру'', хотя, определённо, у вас уже должны быть базовые знания по программированию перед тем, как вы откроете эту книгу.

Programming Perl (O'Reilly and Associates, ISBN 0-937175-64-1) это большой всеобъемлющий справочник по всему языку, соавтором книги является создатель Perl Ларри Уолл (Larry Wall). Там Вы встретите также некоторое количество учебного материала и много длинных практических примеров. Тем не менее, книга предназначена для Гуру, и если вы не программируете в UNIX с 1977 года, как я, она может просто влететь вам в одно ухо и вылететь из другого. (c) LVK

Кроме того, есть очень хорошая группа новостей в Usenet, называемая comp.lang.per

lee7 говорит, что теперь уже comp.lang.perl.misc
, которую читают очень высококлассные специалисты в Perl, включая Ларри Уолла (и вашего покорного слугу). Если у вас имеются сложности с доступом к Usenet, то вместо этого вы можете послать письмо на адрес perl-users-request@virginia.edu и попросить, чтобы вас подписали на список рассылки.
Next Previous Contents