Глава 11. Встроенные функции: ввод/вывод и межпроцессные взаимодействия

Коротко

Ввод и вывод в Perl не ограничивается только работой с консолью (то есть вводом с клавиатуры и выводом на дисплей). Вы можете работать также с файлами, расположенными на диске, и обмениваться информацией с другими процессами. Все эти операции ввода/вывода выполняются с помощью дескрипторов файлов (file handlers). Поскольку к этой теме относится значительный объем материала, я собираюсь разбить его на две главы: в данной главе мы будем иметь дело с консольным вводом/выводом и взаимодействием между процессами (IPC = interprocess communication), а в следующая будет посвящена работе с дисковыми файлами.

Форматы Perl

Perl включает инструмент для создания простых отчетов и таблиц. Это форматы Perl. Форматирование выходного текста для отчетов с помощью форматов изначально было одной из основных задач Perl (вспомните, что название «Perl» происходит от сокращения фразы Practical Extraction and Reporting Language). Используя форматы, можно задать, как информация будет выводиться на консоль, например, выровнять текст по правому, левому краю или по центру, проконтролировать ширину определенных полей и их положение в выходном распечатке, и так далее. (С помощью форматов можно также выводить информацию во внешний файл.)

Форматы Perl являются довольно примитвным инструментом — например, в них нет поддержки стилей (style sheets). Однако, они часто используются в CGI-программировании (CGI = Common Gateway Interface) для создания форматированного текста.

Подобно пакетам и подпрограммам, форматы нуждаются в объявлении (declaration). Чтобы объявить формат, надо задать дескриптор файла, для которого создается формат, перечислить «магические строчки», состоящие из символов $, ^, <. | и т. д. (эти символы указывают, какой именно вы хотите видеть строчку отчета), затем указать в строке имена выводимых переменных. Объявление формата заканчивается точкой (.). Форматированные данные выводятся функцией write.

Рассмотрим пример. Один элемент данных выравнивается по левому краю, второй элемент данных — по правому. Длина «магической строчки» определяет длину строки вывода. При печати оператором write получаем форматированный вывод:

format STDOUT =

@<<<<<<<<<<<@>>>>>>>>>>>>>>>

$text1,     $text2

.

$text1 = "Hello";

$text2 = "there!";

write;             # по умолчанию используется STDOUT

Hello                  there

Строго говоря, формат состоит из строк с текстом, строк с шаблонами, строк данных и строк с комментариями. Подробнее об этом рассказывается в разделе «Форматы: формальный синтаксис».

Взаимодействие процессов — также объемная тема. (Кроме того, ей посвящено не так уж мало книг.) В этой главе мы начнем лишь знакомиться с ней. Для начала мы узнаем, как вызывать системные функции с помощью команд exec и syscall. Затем научимся запускать процессы и передавать им данные, читать передаваемые другими процессами данные, и запускать дочерние процессы, для которых можно выполнять ввод/вывод данных. Мы также посмотрим, как использовать сетевые соединения для пересылки информации через Интернет. Мы даже познакомимся с технологией OLE системы Windows, позволяющей обмениваться информацией с процессами. Однако выше головы не прыгнешь, из-за объемности темы мы все равно сможем бросить лишь поверхностный взгляд на проблемы IPC (то есть, взаимодействия между процессами). Чтобы получить более полную информацию, вам придется обратиться к документации Perl.

Непосредственные решения

print — печать списка данных

Функция print используется для вывода (печать) в указанный дескриптор файла списка данных. Если дескриптор файла опущен, используется STDOUT или поток вывода по умолчанию. (Чтобы назначить поток вывода по умолчанию используется функция select — ее описание можно найти в соответствующем разделе главы 12). Если список данных опущен, используется специальная переменная Perl $_:

print дескриптор-файла список

print список

print

На протяжении этой книги мы не раз встречались с функцией print и уже довольно неплохо с ней знакомы. Функция print возвращает значение истина, если операция печати прошла успешно. Хотя до сих пор мы использовали ее только со стандартным потоком вывода STDOUT, ее можно использовать и для печати информации в другие файлы (как мы увидим в следующей главе). Пример:

$a = "Hello"; $b = " to"; $c = " you";

$d = " from"; $e = " Perl!";

print $a, $b, $c, $d, $e;

Hello to you from Perl!

printf — печать форматированного списка данных

Функция printf используется для форматированного вывода (печати) в указанный дескриптор файла списка данных. Если дескриптор файла опущен, используется STDOUT или поток вывода по умолчанию:

printf дескриптор-файла формат, список

printf формат, список

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

print дескриптор-файла sprintf (формат, список);

за исключением того, что она использует разделитель выходных записей, хранящийся в специальной переменной Perl $\. Поэтому подробнее узнать о том, как организована и как работает строка форматирования формат, можно из раздела «sprintf — форматирование строки» главы 10.

Несколько примеров использования функции printf:

$value = 1234.56789;

printf "%.4f\n", $value

1234.5679

 

printf "%.5f\n", $value

1234.56789

 

printf "%6.6f\n", $value

1234.567890

 

printf "%+.4e\n", $value

+1.2346e+003

(Если ваша программа чувствительна к локальным настройкам (то есть, если операционная система поддерживает локальные установки и в программе есть прагма use locale), то форматирование десятичной точки определяется значением LC_NUMERIC настроек locale.)

Чтение входного потока <>

Для чтения стандартного входного потока данных STDIN вы можете использовать выражение <>, как это делается в следующем случае:

while (<>) {

   print;

}

По умолчанию выражение <> заносит значение в специальную переменную Perl $_. Если вы хотите занести значение в другую переменную, ее надо указать в явном виде:

while ($input = <>) {

   print $input;

}

Выражение <> является краткой формой для <STDIN>. Если вы хотите использовать для ввода другой дескриптор файла, его надо задать как <дескриптор-файла>.

getc — считать одиночный символ

Функция getc возвращает следующий символ, считанный из дескриптора файла. Если дескриптор файла не указан, используется STDIN:

getc дескриптор-файла

getc

Многих программистов разочаровывает невозможность использования функции getc для небуферизованного ввода (то есть символ за символом, по мере ввода пользователем), если только система специально для этого не настраивалась. При обычных обстоятельствах функция getc ожидает, пока пользователь не нажмет клавишу «Ввод», и только потом начинает передавать введенные символы. Однако, в некоторых версиях Unix буферизацию можно отключить — как, например, в следующем коде, который повторяет на экране в эхо-режиме вводимые поьзователем символы, пока их число не достигнет десяти:

system "stty cbreak </dev/tty >&1";

for ($loop_index = 0; $loop_index <= 9; $loop_index++) {

   $char = getc(STDIN);

   print $char;

}

(Этот пример использует функцию system для обращения к системе. Подробнее об этой возможности рассказывается далее в разделе «IPC: system — ветвление и выполнение системной команды».)

write — вывод форматированной записи

Функция write печатает форматированную запись в дескриптор файла с помощью формата, связанного с заданным дескриптором. Если последний опущен, используется STDOUT:

write дескриптор-файла

write выражение

write

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

format STDOUT =

@<<<<<<<<<<<@>>>>>>>>>>>>>>>

$text1,     $text2

.

$text1 = "Hello";

$text2 = "there!";

write;

Hello                  there

Подсказка: Помните, что вы должны заканчивать объявление формата точкой (.).

Форматы: текст, выровненный по левому краю

Чтобы выровнять текст по левому краю в форматируемом поле, используйте символы <, следующие за символом @ (@ начинает поле, предназначенное для подстановки данных). Ширина поля определяется количеством символов < (сам символ @ также включается в ширину поля). Пример:

format STDOUT =

@<<<<<<<<<<<<@<<<<<<<<<<<<@<<<<<<<<@<<<<

$firstname,  $lastname,   $ID,     $extension

.

$firstname = "Cary"; $lastname = "Grant";

$ID = 1024; $extension = x456;

write;

Cary         Grant        1024     x456

Форматы: текст, выровненный по правому краю

Чтобы в форматируемом поле выровнять текст по правому краю, используйте символы >, следующие за @ (символ @ начинает поле, предназначенное для подстановки данных). Как и в предыдущем случае, ширина поля определяется количеством символов > (как и раньше, сам символ @ также включается в ширину поля). Пример:

format STDOUT =

@>>>>>>>>>>>>>>>>>

$text

.

$text = "Hello!";

write;

            Hello!

Форматы: центрированный текст

Если вы хотите в форматируемом поле выровнять текст по центру, чтобы задать ширину поля вместо символов > и < используйте символ |. Пример:

format STDOUT =

@||||||||||||||||||||||||||

$text

.

$text = "Hello!";

write;

          Hello!

Форматы: печать чисел

Чтобы задать поле для вывода числового значения, используйте символы # в сочетании с необязательным символом точки (.). Например, ниже резервируется нужное число знаков для вывода числа «пи»:

$pi = 3.14159253589;

format STDOUT =

@.## @.#######

$pi, $pi

.

write;

3.14 3.1415926

Обратите внимание, что символ # не может стоять на первой позиции в строке: встречая в теле формата строку, начинающуюся с символа #, Perl рассматривает ее как комментарий.

Подсказка: Если вы хотите вывести число без отсечения разрядов, используйте спецификатор поля @*. (При этом такое поле должно быть единственным полем, заданным в «магической строке».)

Форматы: форматированный многострочный вывод

Многострочные форматы не представляют проблемы в Perl. Просто укажите в формате столько «магических строк» и строк с переменными, сколько вам надо, не забыв завершить формат точкой. В следующем примере выводится сразу несколько строк текста:

format STDOUT =

@<<<<<<<<<<<@<<<<<<<<<<<

$text1,     $text2

@<<<<<<<<<<<@<<<<<<<<<<<

$text3,     $text4

.

$text1 = "Hello";

$text2 = "there!";

$text3 = "How are";

$text4 = "things?";

write;

Hello       there

How are     things?

Форматы: форматированный многострочный вывод с вырезкой нужного текста

Чтобы разбить длинную строку текста на несколько полей, вместо символа @. используйте ^ — в этом случае текст, соответствующий полю, вырезается из начала строки, приписанной этому полю (в результате содержимое строки меняется). Число вырезаемых символов соответствует ширине поля. В следующем примере мы вырезаем из одной и той же строки слова «здравствуйте», написанные на разных языках:

$: ="" ;

format STDOUT =

English: ^<<<<<

         $text

German: ^<<<<<<<<<<

        $text

French: ^<<<<<<<<

        $text

.

$text = "Hello!Guten Tag!Bonjour!";

write;

English: Hello!

German: Guten Tag!

French: Bonjour!

Специальная переменная Perl $:, которую мы сделали пустой строкой, содержит символы, принудительно разделяющие фрагменты (это несколько снижает риск ошибиться в длинах фрагментов, объединенных в одну строку и облегчает поиск ошибок). А именно, если еще до достижения нужной длины встретился один из символов, входящих в строку $:, то фрагмент обрывается немедленно. Например:

format STDOUT =

Test = (^<<) (^<<) (^<<)

$text, $text, $text

.

$: = "/";

$text = "111222333"; write;

$text = "11/22/33"; write;

$text = "1/2/3/4"; write;

$text = "111/222/333"; write;

Test: (111) (222) (333)

Test: (11/) (22/) (33/)

Test: (1/ ) (2/ ) (3/ )

Test: (111) (/  ) (222)

В частности, в приведенном чуть выше примере мы могли задать $: = "!". По умолчанию переменная $: содержит два символа — строку "\n-".

Форматы: неформатированный многострочный вывод

Если в качестве «магической строки» формата используется конструкция@*, то заданный текст будет выведен без всякого форматирования. В частности, символы новой строки, указанные в тексте, приведут к тому, что выходе появится несколько строк. Пример:

format STDOUT =

@*

$text

.

$text = "Here\nis\nthe\ntext.";

write;

Here

is

the

text.

Форматы: вывод колонтитула (заголовка страницы)

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

format STDOUT_TOP =

                 Employers

First Name    Last Name   ID       Extension

————————————————————————————————————————————--

.

format STDOUT =

@<<<<<<<<<<<<@<<<<<<<<<<<<@<<<<<<<<@<<<<

$firstname,  $lastname,   $ID,     $extension

.

$firstname = "Cary"; $lastname = "Grant";

$ID = 1024; $extension = x456;

write;

                 Employers

First Name    Last Name    ID       Extension

————————————————————————————————————————————--

Cary         Grant        1024     x456

 

Подсказка: Колонтитул может содержать поля данных, изменяющиеся при выводе каждой страницы — например, номер страницы или индекс, совмещенный с оператором автоприращения ++.

Форматы: вывод нижнего колонтитула

К сожалению, Perl не содержит средств, позволяющих автоматически создавать нижние колонтитулы (footers). Если нижний колонтитул имеет переменную высоту (см. далее раздел «Форматы: формальный синтаксис»), то это очевидным образом препятствует нормальному выводу на печать основной части страницы, особенно если размер колонтитула меняется в результате действия формата. (Например, вычисление выражений, соответствующих полям формата, может изменять переменные, от которых зависит количество строк нижнего колонтитула.)

Однако, если колонтитул имеет постоянный размер, его можно вывести вручную, проверив перед выполнением очередной команды write переменную Perl $- ($FORMAT_LINES_LEFT), показывающую, сколько незаполненных строк осталось на странице. Можно чередовать команды write и print при выводе данных в один и тот же поток вывода, но в таком случае придется корректировать переменную $- вручную, чтобы она правильно отражала количество незаполненных строк, оставшихся на странице.

Форматы: специальные переменные Perl

В Perl имеются несколько переменных, связанных с работой с форматами:

  •        $~ — текущий формат

  •        $^ — текущий формат колонтитула

  •        $= — число строк на странице

  •        $| — если имеет значение истина, используется мгновенный вывод, минуя буфер вывода

  •        $^L — строка, выводимая перед началом очередной страницы, за исключением первой.

    В следующем примере мы задаем новый формат для текущего канала вывода, используя переменную $~:

    format standardformat =

    @||||||||||||||||||||||||||||

    $text

    .

    $text = "Hello!";

    @~ = standardformat;

    write;

                Hello!

    Форматы: формальный синтаксис

    Формат, объявляемый с помощью ключевого слова format, за которым следует имя (обычно совпадающее с именем дескриптора файла) и знак равенства, состоит из строк с текстом, шаблонов (поледержателей), строк данных и строк с комментариями. Имя формата может отсутствовать — в таком случае подразумевается имя STDOUT. Описание формата заканчивается строкой, в которой стоит одиночная точка.

    Строка комментария начинается с символа # в первой позиции. Она просто игнорируется Perl и никак не влияет на работу формата.

    Строка с текстом выводится один к одному, причем можно указывать подряд сколько угодно строк с текстом, не перемежая их строками данных. Строкой с текстом считается строка, в которой нет специальных символов @ или ^, обозначающих поля данных. Если вы хотите использовать эти символы не как маркеры полей данных, а как обычные символы, придется использовать трюк — а именно, указать поле данных, которому соответствует текстовая константа с нужным символом:

    format STDOUT =

    There are @>>>>>> pieces  @  @###.## dollars per each

              $value,       "@", $price

    Строка с шаблонами (называемая нами иногда «магической строкой») содержит, кроме текста, маркеры @ и ^, обозначающие подставляемые (форматируемые) поля. За маркерами @ и ^ обычно следуют символы <, > или |, обозначающие длину поля и выравнивание подставляемого в него текста. Если поле предназначено для числового значения, вместо этих символов используется символ # (позиция для цифры) и, возможно, десятичная точка, указывающие Perl, как именно надо вывести числовое значение. Особую роль играет поле @* — оно должно быть единственным в строке с шаблонами; такое поле указывает Perl, что текст подставляемого значения должен выводиться «как есть», без дополнительного форматирования.

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

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

    Формально говоря, нет необходимости выравнивать данные относительно маркеров @ и ^, как в приводимых нами ранее примерах. Однако такое структурирование облегчает читаемость программы, и в силу этого всячески рекомендуется для применения.

    Каждая строка с шаблонами, равно как и строка с текстом, соответствует ровно одной строке вывода (исключением является строка с конструкцией @*, а также строки с символом или символами ~, о которых рассказывается ниже). Если подставляемое значение (текст) содержит символы новой строки, то весь текст игнорируется, начиная с символа \n, если только он не подставляется вместо конструкции @*. Конструкция @*, указываемая в качестве строки с шаблонами, приводит к тому, что соответствующий текст выводится «как есть» с переходом на новую строку для каждого символа \n, заданного внутри текста.

    Маркер поля ^ обрабатывается особыми образом. Если такому полю данных сопоставляется неопределенное значение, а само поле задается с помощью символов #, оно заполняется пробелами без вывода сообщения об ошибке (в противном случае поле форматируется обычным образом). С другими типами выравнивания (<, >, и |) сопоставленное полю значение служит источником, от которого отрезаются фрагменты необходимой длины. Иными словами, такому полю должна сопоставляться скалярная переменная, содержащая текстовую строку. Обычно одна и та же переменная служит источником для нескольких полей, обозначенных маркером ^. Содержимое такой переменной представляет собой набор текстовых значений необходимой длины, соединенных «встык», которые будут последовательно отрезаться в процессе вывода.

    Подсказка: Поле строки с шаблонами с маркером ^ может завершаться тремя точками (текстом «...»). В таком случае, если размер текста, оставшегося в переменной, превысит размер поля, будут выведены три точки, показывающие, что текст слишком длинный, чтобы уместиться в поле вывода. Если же длина текста соответствует длине поля, завершающие три точки не выводятся. (Такую конструкцию удобно использовать, когда вы отрезаете от переменной последний фрагмент текста.)

    Маркер поля ^ в сочетании с символом ~ позволяет получать переменное число строк вывода при обработке одной строки с шаблонами. Если в теле строки имеется символ ~ (заменяемый при выводе на пробел), то строка игнорируется, если после форматирования полей она состоит исключительно из пробелов. Если в теле строки имеется два таких символа подряд (конструкция ~~), то строка будет повторяться до тех пор, пока не исчерпаются сопоставляемые им данные. (Это не значит, что нельзя использовать поля с маркером @ в сочетании с символом ~. Однако в этом случае сопоставляемые полям @ значения должны быть либо вызовами функций, которые рано или поздно начинают возвращать пустые или неопределенные значения, либо элементами массивов с автоприращением индекса, лиюл любыми другими выражениями с подобными свойствами.)

    Пример:

    format STDOUT =

    Subject:     @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

                 $subject

    Index:       @<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<

                 $index,            $description

    Assigned to: @<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<

                 $programmer,       $description

    ~                               ^<<<<<<<<<<<<<<<<<<

                                    $description

    ~                               ^<<<<<<<<<<<<<<<<<<

                                    $description

    ~                               ^<<<<<<<<<<<<<<<...

                                    $description

    Имена форматов, в отличие от скаляров, массивов, хеш и т.д., не имеют специального префикса. Форматы обладают собственной областью имен, поэтому имя формата может совпадать с именем метки, функции, дескриптора файла или пакета. Это значит, что разрешается иметь формат MyName и метку Myname, причем эти имена не будут перекрываться. (Напомним, что Perl — контекстно-чувствительный язык программирования, то есть, способ интерпретации того или иного имени, определяется контекстом, в котором это имя используется.) Как правило, имя формата совпадает с именем дескриптора файла, связанного с форматом (режим по умолчанию) — поэтому очень часто дескриптор файла и сопутствующий ему формат выглядят как один объект. На самом деле это не так, и пользователь может объединять форматы и дескрипторы файлов с разными именами (см. пример из предыдущего раздела).

    Форматы, как и подпрограммы, имеют глобальную область видимости и являются статическими объектами. Поэтому если вы определите формат с именем STDOUT, вызовите несколько раз команду write, а затем переопределите формат STDOUT, то, во-первых, получите от Perl предупреждающее сообщение «Format STDOUT redefined at ... line ...» на этапе компиляции сценария, а во-вторых, с удивлением обнаружите, что команды write используют последний вариант формата, а не тот, который был определен в начале программы. (Пример смены форматов в процессе вывода приведен в предыдущем разделе.)

    Форматы являются статическими объектами. Это означает, что, вообще говоря, нельзя генерировать формат «на лету» во время выполнения программы и использовать в нем механизм интерполяции, как это делается в случае шаблонов команд m/.../ и s/.../.../. Однако это ограничение можно обойти, скомпилировав формат с помощью функции eval. Пример:

    $cols = 30;

    $format = "format STDOUT =\n"

            . "^" . ("<" x $cols) . "\n"

            . ’$entry’ . "\n"

            . (" " x 8) . "^" . ("<" x ($cols-8)) . "~~\n"

            . (" " x 8) . ’$entry’ . "\n"

            . ".\n";

    print $format if $Debugging;

    eval $format;

    $entry =<>;

    write;

    Данный код динамически создаст формат с нужным количеством колонок вывода, равным $cols+1:

    format STDOUT =

    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    $entry

            ^<<<<<<<<<<<<<<<<<<<<<<~~

            $entry

    .

    warn — вывод предупреждающих сообщений

    До сих пор на протяжении всей главы мы имели дело со стандартными потоками данных STDIN и STDOUT. А как работать с потоком данных STDERR, по умолчанию связанным с консолью? Один из способов вывода в поток STDERR, предназначенный для вывода сообщений об ошибках— использовать команду warn:

    warn список

    Подсказка: Другой способ — это, естественно, печатать непосредственно в поток вывода STDERR с помощью функции print.

    Функция warn выводит сообщение в поток STDERR, но, в отличие от функции die (которая также выводит сообщение в STDERR), она не приводит ни к завершению программы, ни к возникновению ошибки. Если вызвать ее без аргументов, вы получите стандартное предупреждение:

    warn;

    Warning: something is wrong at script.pl line 1.

    Если вы присвоите значение переменной $@, то сообщение будет иным — содержащийся в переменной $@ текст будет выведен вместе с некоторым добавочным сообщением в конце:

    $@ = "Overflow error";

    warn;

    Overflow error ...caught at script.pl line 2.

    Вот пример, в котором функция warn используется с аргументами:

    warn "Something", " is", " rotten", " in", " Denmark";

    Something is rotten in Denmark at script.pl line 1.

    Обратите внимание, что, если задать пустой обработчик предупреждающих сигналов (как это делается, рассказывается в разделе «IPC: как переслать сигнал другому процессу» далее в этой главе), сообщения выведены не будут:

    local $SIG{__WARN__} = sub {};

    IPC: exec — системный вызов

    Функция exec выполняет системный вызов:

    exec список

    exec программа список

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

    Функция exec выполняет системную команду, но никогда не возвращает управление назад, то есть работа сценария прерывается. (Если вы хотите вернуть управление сценарию Perl, используйте функцию system.) Функция возвращает значение ложь и передает управление следующей команде Perl только в одном-единственном случае — а именно, когда системной команды, которую вы хотите выполнить, не существует (и когда она выполняется напрямую, а не через командный интерпретатор). Поскольку использование exec вместо system — типичная ошибка, то при заданном флаге -w Perl будет выводить предупреждающее сообщение, если функция exec вызывается сама по себе, а следом за ней не следует одна из функций warn, exit, die или же конец блока. Если вы действительно хотите использовать функцию exec, это нужно делать, например, следующим образом:

    exec ("del *.bak") or print STDERR "Couldn’t find DEL";

    Список доступных команд зависит от операционной системы. Следующий пример будет работать в случае Windows и Unix:

    exec "echo Hello!";

    Hello!

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

    Вот, например, как это происходит для Unix. Если список аргументов содержит несколько элементов (или если аргумент — это массив, содержащий несколько элементов), функция exec вызывает системную функцию execvp с этим списком в качестве параметра. Если функция получает единственный скалярный аргумент или массив, состоящий из одного элемента, Perl проверяет аргумент на наличие метасимволов (например, символов, перенаправляющие ввод/вывод или же объединяющие несколько команд в один канал). Если они найдены, то Perl вызывает командный интерпретатор по умолчанию. В случае же отсутствия метасимволов Perl разбивает строку на компоненты и передает их execvp.

    IPC: system — ветвление и выполнение системной команды

    Функция system выполняет те же действия, что и exec, за одним маленьким исключением. А именно, текущий процесс сперва разветвляется, порождая дочерний процесс, которому и переадресуется вызов команды операционной системы, а затем функция system ждет, пока этот процесс завершится:

    system список

    system программа список

    Те же самые предостережения, которые говорились относительно передачи функции exec списка аргументов, остаются справедливыми и для функции system (см. предыдущий раздел).

    IPC: как прочитать данные, переданные другой программой

    Предположим, что у вас имеется программа с именем printem, которая печатает слово «Hello!»:

    print ’Hello!’;

    Можем ли мы прочитать, что напечатала наша программа, находясь в другой программе? Да, это можно сделать, используя вывод через канал (pipe). Открывая файл, вы указываете, что в качестве источника данных надо использовать данные, направляемые программой printem в стандартный поток вывода (при этом, естественно, система сама должна запустить программу printem, чтобы получить искомые данные):

    open(FILEHANDLE, "printem |");

    В этом примере, чтобы перенаправить поток вывода программы printem в поток ввода нашей программы, мы использовали функцию open (она рассматривается в следующей главе). Каналы, позволяющие соединять потоки ввода и вывода двух разных программ, являются фундаментальным механизмом IPC. По-видимому, наиболее удачно организовано использование каналов в операционной системе Unix, хотя определенная их поддержка имеется и в версии Perl для платформы Win32.

    Создав канал, мы просто читаем данные программы printem из дескриптора файла, созданного при вызове функции open:

    open(FILEHANDLE, "printem |");

    while (<FILEHANDLE>) {

       print;

    }

    close (FILEHANDLE);

    Hello!

    Обратите внимание, что мы можем использовать функцию open  при создании канала либо для чтения данных из файла, либо для их записи в файл (см. следующий раздел), но не для того и другого одновременно. Чтобы использовать оба действия в одной и той же программе, используйте функцию IPC::Open2 из модуля Perl IPC.

    IPC: как переслать данные другой программе

    Допустим, у вас есть программа readem, которая читает поток данных и печатает на экране все, что получает на входе:

    while (<>) {

       print;

    }

    Как переслать этой программе данные? Вы можете сделать это, указав при открытии файла вместо его имени символ |, стоящий перед именем программы:

    open (FILEHANDLE, "| readem");

    Файл, открытый с помощью функции open подобным образом, позволяет пересылать программе данные, записываемые в поток вывода (дескриптор файла):

    open (FILEHANDLE, "| readem");

    print FILEHANDLE "HELLO!";

    close (FILEHANDLE);

    Hello!

    Как уже было сказано в предыдущем разделе, вы можете использовать функцию open, чтобы создать канал либо для чтения данных из файла или либо для записи данных в файл (см. предыдущий раздел), но не для того и другого одновременно. Чтобы использовать оба действия в одной и той же программе, используйте функцию IPC::Open2 из модуля Perl IPC.

    IPC: вывод данных в дочерний процесс

    С помощью функции open можно создать дочерний процесс и получать от него данные, просто считывая их из некоторого дескриптора файла. Для этого надо открыть файл, указав функции open имя «|-»:

    if (open(CHILDHANDLE, "|-")) ...

    Эта команда создает новый дескриптор файла CHILDHANDLE для дочернего процесса и выполняет ветвление текущего процесса (то есть порождает дочерний процесс). Как дочерний, так и родительский процессы используют один и тот же код Perl, но команда open (CHILDHANDLE, "|-") в дочернем процессе будет возвращать ноль (то есть ложь), так что условный оператор if поможет избежать бесконечной цепочки дочерних процессов (и определить, находимся ли мы в родительском процессе или в дочернем).

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

    if (open(CHILDHANDLE, "|-")) {

       print CHILDHANDLE, "Here is the text.";

       close (CHILDHANDLE);

    } ...

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

    if (open(CHILDHANDLE, "|-")) {

       print CHILDHANDLE, "Here is the text.";

       close (CHILDHANDLE);

    } else {

       $input = <>;

       print $input;

       exit;

    }

    Теперь просмотрим на результат — в нашем примере дочерний процесс выводит текст, полученный им от родительского процесса:

    Here is the text.

    IPC: вывод данных в родительский процесс

    В предыдущем разделе мы в дочернем процессе читали данные, полученные от родительского процесса. Точно так же с помощью функции open вы можете создать дочерний процесс, открыть канал связи между дочерним и родительским процессами и выволить данные в родительский процесс из дочернего. Все, что для этого нужно сделать — при открытии файла задать имя «-|»:

    if (open(CHILDHANDLE, "-|")) ...

    Когда вы передаете функции open аргумент "-|", текущий процесс разветвляется, порождая дочерний процесс. Оба процесса используют один и тот же код Perl, но команда open (CHILDHANDLE, "-|") в дочернем процессе будет возвращать ноль (то есть ложь), так что условный оператор if помогает определить, находимся ли мы в родительском процессе или в дочернем (и избежать бесконечной цепочки дочерних процессов).

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

    if (open(CHILDHANDLE, "-|")) {

       $input = <CHILDHANDLE>;

       print $input;

       close (CHILDHANDLE);

    } ...

    С другой стороны, когда мы находимся в дочернем процессе, то данные родительскому процессу пересылаются просто с помощью команды print, печатающей в стандартный поток вывода:

    if (open(CHILDHANDLE, "-|")) {

       $input = <CHILDHANDLE>;

       print $input;

       close (CHILDHANDLE);

    } else {

       print "Here is the text.";

       exit;

    }

    В результате родительский процесс выведет данные, полученные от дочернего процесса:

    Here is the text.

    IPC: как переслать сигнал другому процессу

    В операционной системе Unix процессы взаимодействуют друг с другом, посылая сигналы. Определить, какие сигналы поддерживаются конкретной реализацией Unix, можно с помощью команды kill -l:

    %kill -l

    HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS

    PIPE ALRM TERM URG STOP TSTP CONT CHLD TTIN TTOU IO XCPU

    XFSZ VTALRM PROF WINCH LOST USR1 USR2

    Perl позволяет перехватывать эти сигналы. Для этого к сигналу надо присоединить обработчик. Это можно сделать с помощью специальной переменной Perl — хеша%SIG: для каждого сигнала в этой хешом есть ключ, а значением ключа служит подпрограмма, обрабатывающая сигнал.

    Рассмотрим пример. В нашем случае мы разветвляем процесс и создаем дочерний процесс. Мы также заставляем дочерний процесс переслать родительскому сигнал INT. Итак, создаем дочерний процесс:

    if (open(CHILDHANDLE, "|-")) ...

    Затем мы добавляем обработчик сигнала INT — анонимную подпрограмму, которая выводит сообщение, когда мы получаем сигнал:

    if (open(CHILDHANDLE, "|-")) {

       $SIG{INT} = sub {print "Got the message.\n"};

       ...

    Дочернему процессу потребуется идентификатор родительского процесса (идентификатор текущего процесса хранится в специальной переменной Perl $$). Поэтому мы пересылаем дочернему процессу нужную информацию:

    if (open(CHILDHANDLE, "|-")) {

       $SIG{INT} = sub {print "Got the message.\n"};

       print CHILDHANDLE "$$";

       close (CHILDHANDLE);

       ...

    В дочернем процессе мы запоминаем идентификатор родительского процесса в переменной $parentid и посылаем родительскому процессу сигнал INT с помощью функции kill:

    if (open(CHILDHANDLE, "|-")) {

       $SIG{INT} = sub {print "Got the message.\n"};

       print CHILDHANDLE "$$";

       close (CHILDHANDLE);

    } else {

       chomp($parentid = <>);

       kill INT => $parentid;

       exit;

    }

    А вот и результат: родительский процесс печатает сообщение о получении сигнала от дочернего процесса:

    Got the message.

    IPC: использование сокетов<$FЭтот, а также последующий разделы, используют понятия и синтаксические конструкции объектно-ориентированного программирования, которые пока не рассматривались в данной книге. Если вы уже знаете, что такое пакеты, модули, классы, методы и объекты в языке Perl — читайте смело. Если вы этого еще не знаете — пропустите эти два раздела и вернитесь к ним позже, когда ознакомитесь с главами 13–16. — Примеч. перев.>

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

    Поэтому вместо детальных разъяснений я просто приведу пример, в котором программа-клиент пересылает данные программе-серверу через Сеть, используя протокол UDP. При этом хотя вы можете запускать программу-клиента с любой машины, подключенной к Интернет (например, с домашнего персонального компьютера), программа-сервер должна работать на машине провайдера.

    Наша программа-клиент начинается с импортирования модуля IO::Socket стандартного пакета IO, входящего в состав Perl:

    use IO::Socket;

    Затем мы создаем сокет с помощью вызова метода IO::Socket::INET->new. Мы передаем методу в качестве параметров используемый протокол (UDP), порт, через который мы хотим получить доступ к серверу, и имя машины-провайдера (ISP):

    use IO::Socket;

    $socket = IO::Socket::INET->new(Proto => ’udp’,

                     PeerPort => 4321,

                     PeerAddr => ’servername.com’);

    (В качестве номера порта в нашем примере стоит произвольное значение 4321. В операционной системе Unix порты меньше 1024 зарезервированы для системного пользования.)

    Нам осталось переслать данные на сервер. Мы осуществляем это с помощью метода send:

    use IO::Socket;

    $socket = IO::Socket::INET->new(Proto => ’udp’,

                     PeerPort => 4321,

                     PeerAddr => ’servername.com’);

    $socket->send(’Hello!’);

    Осталось написать программу-сервер. Она уже должна работать, когда программы-клиент посылает сообщение. Поэтому программа-сервер начинается с импортирования модуля и печати сообщения «Waiting...»:

    use IO::Socket;

    print "Waiting ...\n";

    Мы создаем сетевое соединение для программы-сервера с помощью метода IO::Socket::INET->new, указывая как параметры протокол UDP и порт с номером 4321 (тот же самый, который использует программа-клиент):

    use IO::Socket;

    print "Waiting ...\n";

    $socket = IO::Socket::INET->new(Proto => ’udp’,

                     LocalPort => 4321);

    Чтобы получить данные от клиента, мы используем метод recv объекта $socket. При вызове метода мы указываем, что хотим получить максимум 128 байт. Метод recv будет ожидать данные, и выведет их сразу по получении:

    use IO::Socket;

    print "Waiting ...\n";

    $socket = IO::Socket::INET->new(Proto => ’udp’,

                     LocalPort => 4321);

    $socket->recv($text, 128);

    print "Got this message: $text\n";

    Теперь после того, как будет запущена программа-клиент, программа-сервер напечатает:

    Waiting ...

    Got this message: Hello!

    IPC: использование технологии Win32 OLE Automation<$F Предыдущий раздел был лишь наброском рассказа, что же такое сетевые соединения и как с ними работать в Perl. Точно так же этот раздел — не более как набросок того, как в Perl используется технология Win32 OLE Automation фирмы Microsoft. — Примеч. перев.>

    Одно из типичных применений IPC на Windows-машинах — это использование серверов OLE Automation (их также называют еще «компонентами»). Программа ActivePerl (платформа Win32) поддерживает OLE Automation. Чтобы использовать эту технологию, необходимо создать в программе объект с помощью сервера OLE Automation (например, Microsoft Excel). Затем можно использовать его методы.

    При взаимодействии процессов в системе Windows внутреннее представление типов данных становится проблемой — представьте себе, к примеру, что ваша программа написана на C++ и вы взаимодействуете с программой, написанной на Pascal. (Если вы знаете оба языка, то легко сообразите, что, например, внутреннее представление строк у компилятора Borland Pascal и компилятора Visual C++ принципиально различно.) Чтобы справиться с пробемой перекодировки внутреннего представления, пакет Perl for Win32 определяет набор стандартных вариантных типов данных. Эти типы, перечисленные в таблице 11.1, обеспечивают удобный способ хранения по сути нетипизированных данных Perl. Таблица 11.1 показывает типы данных OLE, в которые преобразуются внутренние данные Perl перед пересылкой серверу OLE Automation.

    Например, значение, хранимое в Perl как целое число, будет преобразовано в VT_I4 типа данных, число с плавающей точкой (двойной точности) — в VT_I8, и так далее. Пакет Perl for Win32 заботится об этом автоматически.

    Чтобы создать объект OLE Automation, надо подключить модуль Perl OLE:

    use OLE;

    Теперь вы можете использовать функцию CreateObject, чтобы создать объект OLE Automation. Рассмотрим небольшой пример. Здесь мы будем использовать программу Microsoft Excel для того, чтобы сложить 2 + 2 (воистину правильное использование данной программы ) и вывести результат. Для начала сохраним эти два значения как $operand1 и $operand2 (обратите внимание, что мы сохраняем их как строки):

    use OLE;

    $operand1 = ’2’;

    $operand2 = ’2’;

    Теперь создадим с помощью функции CreateObject объект OLE Automation $excelobject. Этой функции вы передаете значение OLE (содержится в импортированном модуле) и указываете с помощью строки вида ‘сервер.класс’ тип объекта, который хотите создать. (Здесь сервер — это зарегистрированное в Windows имя сервера OLE Automation, а класс — определенный для данного сервера класс создаваемого объекта.) Таким образом, мы создадим электронную таблицу Excel с помощью команды:

    use OLE;

    $operand1 = ’2’;

    $operand2 = ’2’;

    $excelobject = CreateObject OLE ’Excel.Sheet’;

    (Вообще говоря, серверы OLE Automation обычно поддерживают множество классов. Если удача с вами, документация вашего сервера описывает, какие именно методы можно использовать и с какими именно классами. Документация Excel такую информацию содержит — вот почему он был  в нашем примере.)

    Вы создали объект, и теперь свободно можете использовать его методы. В следующем фрагменте кода $operand1 загружается в ячейку электронной таблицы (1,1), $operand2 — в ячейку (2,1), а затем с помощью формулы Excel оба аргумента складываются, а результат помещается в ячейку (3,1):

    use OLE;

    $operand1 = ’2’;

    $operand2 = ’2’;

    $excelobject = CreateObject OLE ’Excel.Sheet’;

    $excelobject->Cells(1,1)->{Value} = $operand1;

    $excelobject->Cells(2,1)->{Value} = $operand2;

    $excelobject->Cells(3,1)->{Formula} = ’=R1C1 + R2C1’;

    Все, что осталось сделать — это вывести сумму и завершить работу<$FПоскольку приложения MS Office любят игнорировать команду quit, возможно, придется также нажать три клавиши Ctrl-Alt-Del и с помощью появившегося меню завершить работу приложения Excel. — Примеч. ред.>:

    use OLE;

    $operand1 = ’2’;

    $operand2 = ’2’;

    $excelobject = CreateObject OLE ’Excel.Sheet’;

    $excelobject->Cells(1,1)->{Value} = $operand1;

    $excelobject->Cells(2,1)->{Value} = $operand2;

    $excelobject->Cells(3,1)->{Formula} = ’=R1C1 + R2C1’;

    $sum = $excelobject->Cells(3,1)->{Value};

    $excelobject->Quit();

    print "According to Microsoft Excel, ",

          "$operand1 + $operand2 = $sum.\n";

    According to Microsoft Excel, 2 + 2 = 4

    Таблица 11.1. Типы данных OLE Automation.

     

    Тип данных OLE

    Стандартный тип данных

     

     

    OLE::VT_BOOL

    Булевский тип OLE

     

     

    OLE::VT_BSTR

    Строка OLE (char* в стиле языка С)

     

     

    OLE::VT_CY

    Денежный тип OLE

     

     

    OLE::VT_DATE

    Дата OLE

     

     

    OLE::VT_I2

    Целое со знаком (2 байта)

     

     

    OLE::VT_I4

    Целое со знаком (4 байта)

     

     

    OLE::VT_R4

    Вещественное (4 байта)

     

     

    OLE::VT_R8

    Вещественное (8 байт)

     

     

    OLE::VT_UI1

    Символ (однобайтовое целое без знака)