Next Previous Contents

Unix Review Column 22 -- Эффективное использование CPAN

Randal Schwartz

Октябрь 1998

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

Вы можете сохранить много времени эффективно используя модули написанные другими людьми. Многие модули включены в дистрибутив Perl, но огромное количество модулей доступно (свободно!) через Comprehensive Perl Archive Network (Всеобъемлющую Сеть Архивов Perl, называемую также CPAN). Если для вас нова идея загружаемых модулей для Perl, то для получения информации о том, что доступно для получения, вы должны просмотреть информацию по адресу http://www.perl.com/CPAN/CPAN.html.

Установка модулей с CPAN бала сделано более легкой с написанием модуля CPAN.pm (поставляемом с to Perl). Например, если нам с CPAN нужен модуль Foo::Bar, то это легко сделать набрав:

      $ perl -MCPAN -eshell

    cpan shell -- CPAN exploration and modules installation (vX.XX)
    ReadLine support enabled

    cpan> install Foo::Bar
    .......
    [messages about fetching, unpacking,
    compiling, testing, and installing appear here]

    cpan> quit

Когда вы в первый раз выполняете эти команды, вас могут попросить ответить на некоторые вопросы о том как получать данные из сети, или о том, где находится ближайший архив CPAN. Если вы не уверены, то используйте http://www.perl.com/CPAN/. Также, если вы не являетесь системным администратором, то вам необходимо добавить строку PREFIX=/путь/куда/вы/имеете/право/записи к параметру настройки makepl_arg для установки двоичных файлов, модулей и документации в каталоги, расположенные ниже PREFIX, в не в системные каталоги. Смотрите perldoc CPAN для получения дополнительной информации.

Давайте глянем на одну из задач, выполнение которой стало намного более легким при использовании CPAN. Когда-то, я размышлял о группе новостей rec.humor.funny, в которой проходило не более двух сообщений в день с относительно интересными шутками. Но я не всегда мог читать эту группу новостей каждый день и я пропускал некоторые шутки, поскольку они удалялись с сервера новостей до того, как я мог прочитать их.

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

Graham Barr написал великолепный модуль с названием Net::NNTP, который обрабатывает общение с сервером NNTP внутри, скрывая механизм от пользователя. Если вы его еще не установили, то это легко сделать, поскольку он находится на CPAN!

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

    use Net::NNTP;

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

    my $SERVER = "nntp.your-isp-goes-here.com";
    my $GROUP = "rec.humor.funny";

Имя сервера должно соответствовать тому, откуда вы читаете новости.

Теперь нам необходимо подключиться к серверу, используя предоставленный Net::NNTP код. Это позволит нам ``разговаривать'' с сервером через объект подключения, этот объект сохраняется в переменной $c:

    my $c = Net::NNTP->new($SERVER)
      or die "Cannot open NNTP: $!";

Теперь нам необходимо перейти к группе rec.humor.funny:

    my ($arts,$low,$high) =
      $c->group($GROUP)
      or die "Cannot go to $GROUP: $!";

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

    foreach my $artnum ($low..$high) {
      my $art = $c->article($artnum) or next;

Если статья не существует (возможно ее отменили или прошел срок хранения статьи), то мы переходим к следующему номеру статьи. Значение $art равно либо undef, либо является ссылкой на список, указывающей на текст статьи. Если мы получаем ссылку на список, то мы просто выдаем эту статью.

      print "=== article $artnum ===\n";
      print @$art;
    }

Это будет хорошая программа, которая правильно выдает все статьи в rec.humor.funny. Хотя их формат не является хорошим... в каждом сообщении он содержит все заголовки и безобидные общие .signature. Они также включают все административные сообщения. Давайте исправим это упущение.

Мы вероятно могли бы написать некоторое быстрое регулярное выражение для изменения текста статьи, но сначала установим дополнительные ресурсы с CPAN. В нашем случае это модуль Mail::Internet, который также написан Graham Barr, который понимает почтовые сообщения в формате описанном RFC822, который также является форматом сообщений в группах новостей. Теперь наша простая программа начинается примерно так:

    use Net::NNTP;
    use Mail::Internet;

    my $SERVER = "nntp.your-isp-goes-here.com";
    my $GROUP = "rec.humor.funny";

    my $c = Net::NNTP->new($SERVER)
      or die "Cannot open NNTP: $!";
    my ($arts,$low,$high) =
      $c->group($GROUP)
      or die "Cannot go to $GROUP: $!";

За исключением дополнительной строки use, этот код является тем же самым. Нам также необходим тот же самый цикл извлечения статей:

    for my $artnum ($low..$high) {
      my $art = $c->article($artnum) or next;

Но здесь мы отклонимся от нашего старого кода. Мы берем ссылку на список, помещенный в переменную $art, и создадим из нее объект соответствующий почтовому сообщению:

      my $mail = Mail::Internet->new($art);

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

      next if $mail->head->get("From") =~ /netfunny\.com/;

Выражение $mail-&gt;head возвращает объект Mail::Header для заданного сообщения, который имеет метод get для выделения отдельного поля. Если он соответствует домену административных сообщений, то мы пропускаем это сообщение и переходим к следующему. Далее мы выдаем тот же самый заголовок, что и в предыдущем варианте:

      print "=== article $artnum ===\n";

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

      $mail->remove_sig;
      $mail->tidy_body;

И печатать только те заголовки, в которых мы заинтересованы (заголовок, дата и оригинальный автор):

      for my $tag (qw(Subject Date From)) {
        print "$tag: ", $mail->head->get($tag);
      }

И в заключение выдадим очищенное от лишней информации тело сообщения:

      print "\n", @{$mail->body};
    }

Вот. Великолепно, без лишних трудностей. Таким образом мы имеем полный вывод всех сообщений в rec.humor.funny, которые находятся на сервере новостей, и которые очищены от лишней информации, что далее?

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

Нам необходимо некоторое количество памяти для того, чтобы мы знали, какие статью мы уже выдали. К счастью существует стандартное место для хранение, которое называется файлом newsrc, который имеет стандартный формат, который понимают большинство программ чтения новостей. Поскольку существует общий формат, то существует модуль, распространяемый через CPAN, который умеет работать с ним. В нашем случае это модуль News::Newsrc, написанный Steven McDougall.

Так что добавим в программу сохранение данных:

    use Net::NNTP;
    use Mail::Internet;
    use News::Newsrc;

    my $SERVER = "nntp.your-isp-goes-here.com";
    my $GROUP = "rec.humor.funny";

Далее в начале нашей программы мы добавили строку use для использования свежего модуля. Далее мы подключим файл newsrc создавая объект newsrc:

    my $newsrc = News::Newsrc->new;
    $newsrc->load;

Что нас приводит к точке... мы используем тот же файл newsrc, который также используется моей программой чтения новостей, что означает, что эта программа будет знать какие статьи из rec.humor.funny я уже читал, используя данную программу, или программу чтения новостей! великолепно.

Некоторый неизмененный код из предыдущей версии:

    my $c = Net::NNTP->new($SERVER)
      or die "Cannot open NNTP: $!";
    my ($arts,$low,$high) =
      $c->group($GROUP)
      or die "Cannot go to $GROUP: $!";

Но мы теперь не хотим выполнять цикл по всем статьям начиная с $low и до $high. Мы хотим получить только те статьи, которые мы еще не видели. Эти статьи называются неотмеченными на жаргоне newsrc, так что мы будем использовать метод с соответствующим именем:

    my @unmarked =
      $newsrc->unmarked_articles
      ($GROUP, $low, $high);

Теперь @unmarked является списком номеров статей, которые потенциально находятся на сервере новостей (которые не были отменены) и которые еще не были прочитаны мной. Давайте выполним цикл по этим значениям:

    for my $artnum (@unmarked) {
      my $art = $c->article($artnum) or next;

Если статья была извлечена, то отметим ее. Я увижу ее только один раз:

      $newsrc->mark($GROUP, $artnum);

И теперь остаток тела цикла выглядит также как и в предыдущей версии:

      my $mail = Mail::Internet->new($art);
      next if $mail->head->get("From") =~ /netfunny\.com/;
      print "=== article $artnum ===\n";
      $mail->remove_sig;
      $mail->tidy_body;
      for my $tag (qw(Subject Date From)) {
        print "$tag: ", $mail->head->get($tag);
      }
      print "\n", @{$mail->body};
    }

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

    $newsrc->save;

Так что достаточно короткая программа теперь является небольшой программой чтения новостей, обновляя файл newsrc и даже отражая ненужные нам административные статьи. И ее написание и отладка заняла у меня полчаса. Это подчеркивает силу CPAN. Наслаждайтесь!


Next Previous Contents